Skip to main content

reth_network/
eth_requests.rs

1//! Blocks/Headers management for the p2p network.
2
3use crate::{
4    budget::DEFAULT_BUDGET_TRY_DRAIN_DOWNLOADERS, metered_poll_nested_stream_with_budget,
5    metrics::EthRequestHandlerMetrics,
6};
7use alloy_consensus::{constants::KECCAK_EMPTY, BlockHeader, ReceiptWithBloom};
8use alloy_eips::BlockHashOrNumber;
9use alloy_primitives::{Bytes, B256};
10use alloy_rlp::{length_of_length, Encodable};
11use futures::StreamExt;
12use reth_eth_wire::{
13    message::MAX_MESSAGE_SIZE,
14    snap::{
15        AccountData, AccountRangeMessage, BlockAccessListsMessage, ByteCodesMessage,
16        GetAccountRangeMessage, GetStorageRangesMessage, SnapProtocolMessage, StorageData,
17        StorageRangesMessage,
18    },
19    BlockAccessLists, BlockBodies, BlockHeaders, Cells, EthNetworkPrimitives, GetBlockAccessLists,
20    GetBlockBodies, GetBlockHeaders, GetCells, GetNodeData, GetReceipts, GetReceipts70,
21    HeadersDirection, NetworkPrimitives, NodeData, Receipts, Receipts69, Receipts70,
22};
23use reth_network_p2p::{
24    error::{RequestError, RequestResult},
25    snap::client::SnapResponse,
26};
27use reth_network_peers::PeerId;
28use reth_primitives_traits::Block;
29use reth_storage_api::{
30    errors::provider::ProviderResult, BalProvider, BlockReader, BytecodeReader,
31    GetBlockAccessListLimit, HeaderProvider, RangeEnd, RangeResponse, StateProviderFactory,
32    StateRangeProviderFactory,
33};
34use reth_transaction_pool::{blobstore::NoopBlobStore, BlobStore};
35use std::{
36    collections::VecDeque,
37    future::Future,
38    pin::Pin,
39    task::{Context, Poll},
40    time::Duration,
41};
42use tokio::{
43    sync::{mpsc::Receiver, oneshot},
44    task::JoinHandle,
45    time::{sleep_until, Instant, Sleep},
46};
47use tokio_stream::wrappers::ReceiverStream;
48use tracing::{debug, error, Span};
49
50// Limits: <https://github.com/ethereum/go-ethereum/blob/b0d44338bbcefee044f1f635a84487cbbd8f0538/eth/protocols/eth/handler.go#L34-L56>
51
52/// Maximum number of receipts to serve.
53///
54/// Used to limit lookups.
55pub const MAX_RECEIPTS_SERVE: usize = 1024;
56
57/// Maximum number of block headers to serve.
58///
59/// Used to limit lookups.
60pub const MAX_HEADERS_SERVE: usize = 1024;
61
62/// Maximum number of block headers to serve.
63///
64/// Used to limit lookups. With 24KB block sizes nowadays, the practical limit will always be
65/// `SOFT_RESPONSE_LIMIT`.
66pub const MAX_BODIES_SERVE: usize = 1024;
67
68/// Maximum number of block access lists to serve.
69///
70/// Used to limit lookups.
71pub const MAX_BLOCK_ACCESS_LISTS_SERVE: usize = 1024;
72
73/// Maximum number of cell lookups to serve.
74///
75/// Used to limit lookups.
76pub const MAX_CELLS_SERVE: usize = 1024;
77
78/// Maximum number of bytecode lookups to serve.
79///
80/// Used to limit lookups.
81pub const MAX_BYTE_CODES_SERVE: usize = 1024;
82
83/// Maximum number of storage range account lookups to serve.
84pub const MAX_STORAGE_RANGE_ACCOUNTS_SERVE: usize = 1024;
85
86/// Maximum size of replies to data retrievals: 2MB
87pub const SOFT_RESPONSE_LIMIT: usize = 2 * 1024 * 1024;
88
89/// Maximum number of `snap/2` account and storage range requests served at once.
90///
91/// Serving an old root can rewind the trie block by block, so these requests run off the handler
92/// task and are capped to keep them from starving other blocking work.
93const MAX_CONCURRENT_STATE_RANGE_REQUESTS: usize = 2;
94
95/// Maximum number of `snap/2` range requests waiting for a serving slot, one per peer.
96///
97/// They wait because snap clients read an empty response as the root being unavailable, and then
98/// leave the peer alone until they retry.
99const MAX_QUEUED_STATE_RANGE_REQUESTS: usize = 16;
100
101/// How long a `snap/2` range request may wait for a serving slot before it is answered empty.
102///
103/// A requester's timeout adapts down to two seconds and also covers transit and serving, so the
104/// wait takes only a fraction of it. A slot is otherwise spent on a response nobody awaits.
105const STATE_RANGE_REQUEST_QUEUE_TIMEOUT: Duration = Duration::from_millis(500);
106
107/// Upper bound on the headers reserved once the first requested header resolves.
108///
109/// Header responses are bound by [`MAX_HEADERS_SERVE`] rather than by size, so a full batch reaches
110/// its final capacity within a few doublings while a batch that runs past the local tip wastes at
111/// most this many slots.
112const MAX_HEADERS_RESERVE: usize = 64;
113
114/// Upper bound on the block bodies or receipt lists reserved once the first requested block
115/// resolves.
116///
117/// These responses are bound by [`SOFT_RESPONSE_LIMIT`], so the first block's size predicts how
118/// many fit and this only caps the estimate for tiny blocks.
119const MAX_BLOCKS_RESERVE: usize = 16;
120
121/// Manages eth related requests on top of the p2p network.
122///
123/// This can be spawned to another task and is supposed to be run as background service.
124#[derive(Debug)]
125#[must_use = "Manager does nothing unless polled."]
126pub struct EthRequestHandler<C, N: NetworkPrimitives = EthNetworkPrimitives> {
127    /// The client type that can interact with the chain.
128    client: C,
129    /// Blob store used for serving blob cell requests.
130    blob_store: Box<dyn BlobStore>,
131    /// Incoming request from the [`NetworkManager`](crate::NetworkManager).
132    incoming_requests: ReceiverStream<IncomingEthRequest<N>>,
133    /// Metrics for the eth request handler.
134    metrics: EthRequestHandlerMetrics,
135    /// `snap/2` range requests running or waiting off the handler task.
136    state_range_requests: StateRangeRequests,
137}
138
139// === impl EthRequestHandler ===
140impl<C, N: NetworkPrimitives> EthRequestHandler<C, N> {
141    /// Create a new instance
142    pub fn new(client: C, incoming: Receiver<IncomingEthRequest<N>>) -> Self {
143        Self {
144            client,
145            blob_store: Box::<NoopBlobStore>::default(),
146            incoming_requests: ReceiverStream::new(incoming),
147            metrics: Default::default(),
148            state_range_requests: Default::default(),
149        }
150    }
151
152    /// Set blob store for the request handler
153    pub fn with_blob_store(mut self, blob_store: Box<dyn BlobStore>) -> Self {
154        self.blob_store = blob_store;
155        self
156    }
157}
158
159impl<C, N> EthRequestHandler<C, N>
160where
161    N: NetworkPrimitives,
162    C: BlockReader,
163{
164    /// Returns the list of requested headers
165    fn get_headers_response(&self, request: GetBlockHeaders) -> Vec<C::Header> {
166        let GetBlockHeaders { start_block, limit, skip, direction } = request;
167
168        let mut headers = Vec::new();
169
170        let mut block: BlockHashOrNumber = match start_block {
171            BlockHashOrNumber::Hash(start) => start.into(),
172            BlockHashOrNumber::Number(num) => {
173                let Some(hash) = self.client.block_hash(num).unwrap_or_default() else {
174                    return headers
175                };
176                hash.into()
177            }
178        };
179
180        let skip = skip as u64;
181        let mut total_bytes = 0;
182
183        for _ in 0..limit {
184            if let Some(header) = self.client.header_by_hash_or_number(block).unwrap_or_default() {
185                let number = header.number();
186                let parent_hash = header.parent_hash();
187                let len = header.length();
188
189                if headers.is_empty() {
190                    let count = limit.min(MAX_HEADERS_SERVE as u64) as usize;
191                    headers.reserve(response_reserve_hint(count, len, MAX_HEADERS_RESERVE));
192                }
193
194                total_bytes += len;
195                headers.push(header);
196
197                if headers.len() >= MAX_HEADERS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
198                    break
199                }
200
201                match direction {
202                    HeadersDirection::Rising => {
203                        if let Some(next) = number.checked_add(1).and_then(|n| n.checked_add(skip))
204                        {
205                            block = next.into()
206                        } else {
207                            break
208                        }
209                    }
210                    HeadersDirection::Falling => {
211                        if skip > 0 {
212                            // prevent under flows for block.number == 0 and `block.number - skip <
213                            // 0`
214                            if let Some(next) =
215                                number.checked_sub(1).and_then(|num| num.checked_sub(skip))
216                            {
217                                block = next.into()
218                            } else {
219                                break
220                            }
221                        } else {
222                            block = parent_hash.into()
223                        }
224                    }
225                }
226            } else {
227                break
228            }
229        }
230
231        headers
232    }
233
234    fn on_headers_request(
235        &self,
236        _peer_id: PeerId,
237        request: GetBlockHeaders,
238        response: oneshot::Sender<RequestResult<BlockHeaders<C::Header>>>,
239    ) {
240        self.metrics.eth_headers_requests_received_total.increment(1);
241        let headers = self.get_headers_response(request);
242        let _ = response.send(Ok(BlockHeaders(headers)));
243    }
244
245    fn on_bodies_request(
246        &self,
247        _peer_id: PeerId,
248        request: GetBlockBodies,
249        response: oneshot::Sender<RequestResult<BlockBodies<<C::Block as Block>::Body>>>,
250    ) {
251        self.metrics.eth_bodies_requests_received_total.increment(1);
252        let count = request.0.len();
253        let mut bodies = Vec::new();
254
255        let mut total_bytes = 0;
256
257        for hash in request {
258            if let Some(block) = self.client.block_by_hash(hash).unwrap_or_default() {
259                let body = block.into_body();
260                let len = body.length();
261                if bodies.is_empty() {
262                    bodies.reserve(response_reserve_hint(count, len, MAX_BLOCKS_RESERVE));
263                }
264                total_bytes += len;
265                bodies.push(body);
266
267                if bodies.len() >= MAX_BODIES_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
268                    break
269                }
270            } else {
271                break
272            }
273        }
274
275        let _ = response.send(Ok(BlockBodies(bodies)));
276    }
277
278    /// Replies to `GetNodeData`.
279    ///
280    /// State serving via eth `GetNodeData` was removed; answer with an empty payload so eth/66
281    /// peers receive a response instead of hanging until the request timeout when the oneshot is
282    /// dropped unanswered.
283    fn on_node_data_request(
284        &self,
285        _peer_id: PeerId,
286        _request: GetNodeData,
287        response: oneshot::Sender<RequestResult<NodeData>>,
288    ) {
289        self.metrics.eth_node_data_requests_received_total.increment(1);
290        let _ = response.send(Ok(NodeData(vec![])));
291    }
292
293    fn on_receipts_request(
294        &self,
295        _peer_id: PeerId,
296        request: GetReceipts,
297        response: oneshot::Sender<RequestResult<Receipts<C::Receipt>>>,
298    ) {
299        self.metrics.eth_receipts_requests_received_total.increment(1);
300
301        let receipts = self.get_receipts_response(request, |receipts_by_block| {
302            receipts_by_block.into_iter().map(ReceiptWithBloom::from).collect::<Vec<_>>()
303        });
304
305        let _ = response.send(Ok(Receipts(receipts)));
306    }
307
308    fn on_receipts69_request(
309        &self,
310        _peer_id: PeerId,
311        request: GetReceipts,
312        response: oneshot::Sender<RequestResult<Receipts69<C::Receipt>>>,
313    ) {
314        self.metrics.eth_receipts_requests_received_total.increment(1);
315
316        let receipts = self.get_receipts_response(request, |receipts_by_block| {
317            // skip bloom filter for eth69
318            receipts_by_block
319        });
320
321        let _ = response.send(Ok(Receipts69(receipts)));
322    }
323
324    /// Handles partial responses for [`GetReceipts70`] queries.
325    ///
326    /// Whole blocks are packed until the response reaches [`SOFT_RESPONSE_LIMIT`]. The last block
327    /// is then served up to [`MAX_MESSAGE_SIZE`] and flagged incomplete only when its receipts
328    /// overflow that limit, as required by the devp2p spec. A response never ends with an empty
329    /// incomplete block: if not even the next receipt fits within the message, the response ends
330    /// after the last complete block. If the first receipt alone cannot fit within
331    /// [`MAX_MESSAGE_SIZE`], including the response framing, the response is empty and complete.
332    fn on_receipts70_request(
333        &self,
334        _peer_id: PeerId,
335        request: GetReceipts70,
336        response: oneshot::Sender<RequestResult<Receipts70<C::Receipt>>>,
337    ) {
338        self.metrics.eth_receipts_requests_received_total.increment(1);
339
340        let GetReceipts70 { first_block_receipt_index, block_hashes } = request;
341
342        let count = block_hashes.len();
343        let mut receipts = Vec::new();
344        let mut total_bytes = 0usize;
345        let mut last_block_incomplete = false;
346
347        // The request ID is unavailable here. Reserve the message code, the largest request ID,
348        // the incomplete flag, and the three RLP list headers to bound the full encoded response.
349        let max_receipts_size = MAX_MESSAGE_SIZE -
350            (1 + u64::MAX.length() + false.length() + 3 * length_of_length(MAX_MESSAGE_SIZE));
351
352        for (idx, hash) in block_hashes.into_iter().enumerate() {
353            if idx >= MAX_RECEIPTS_SERVE {
354                break
355            }
356
357            let Some(mut block_receipts) =
358                self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
359            else {
360                break
361            };
362
363            if idx == 0 && first_block_receipt_index > 0 {
364                let skip = first_block_receipt_index as usize;
365                if skip >= block_receipts.len() {
366                    block_receipts.clear();
367                } else {
368                    block_receipts.drain(0..skip);
369                }
370            }
371
372            let block_size = block_receipts.length();
373            if receipts.is_empty() {
374                receipts.reserve(response_reserve_hint(count, block_size, MAX_BLOCKS_RESERVE));
375            }
376
377            if total_bytes + block_size <= SOFT_RESPONSE_LIMIT {
378                total_bytes += block_size;
379                receipts.push(block_receipts);
380                continue;
381            }
382
383            // This block crosses the soft limit, so it is the last one served. The devp2p spec only
384            // allows `lastBlockIncomplete` when its receipts would overflow the message, so it gets
385            // the remaining message budget.
386            let remaining = block_receipts.len();
387            let mut partial_block = Vec::new();
388            for receipt in block_receipts {
389                let receipt_size = receipt.length();
390                if total_bytes + receipt_size > max_receipts_size {
391                    break;
392                }
393                total_bytes += receipt_size;
394                partial_block.push(receipt);
395            }
396
397            // An empty partial block delivers nothing, so it is never sent. The requester can
398            // simply continue with this block in its next request.
399            if !partial_block.is_empty() {
400                last_block_incomplete = partial_block.len() < remaining;
401                receipts.push(partial_block);
402            }
403            break;
404        }
405
406        let _ = response.send(Ok(Receipts70 { last_block_incomplete, receipts }));
407    }
408
409    #[inline]
410    fn get_receipts_response<T, F>(&self, request: GetReceipts, transform_fn: F) -> Vec<Vec<T>>
411    where
412        F: Fn(Vec<C::Receipt>) -> Vec<T>,
413        T: Encodable,
414    {
415        let count = request.0.len();
416        let mut receipts = Vec::new();
417        let mut total_bytes = 0;
418
419        for hash in request {
420            if let Some(receipts_by_block) =
421                self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
422            {
423                let transformed_receipts = transform_fn(receipts_by_block);
424                let len = transformed_receipts.length();
425                if receipts.is_empty() {
426                    receipts.reserve(response_reserve_hint(count, len, MAX_BLOCKS_RESERVE));
427                }
428                total_bytes += len;
429                receipts.push(transformed_receipts);
430
431                if receipts.len() >= MAX_RECEIPTS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
432                    break
433                }
434            } else {
435                break
436            }
437        }
438
439        receipts
440    }
441
442    fn on_cells_request(
443        &self,
444        _peer_id: PeerId,
445        request: GetCells,
446        response: oneshot::Sender<RequestResult<Cells>>,
447    ) {
448        let mut cells_response = Cells { cell_mask: request.cell_mask, ..Default::default() };
449        let mut total_bytes = 0;
450        let cell_mask = request.cell_mask();
451
452        for hash in request.hashes.into_iter().take(MAX_CELLS_SERVE) {
453            let Some(cells) = self.blob_store.get_cells(hash, cell_mask).unwrap_or_default() else {
454                continue;
455            };
456
457            total_bytes += hash.length() + cells.length();
458            cells_response.hashes.push(hash);
459            cells_response.cells.push(cells);
460
461            if total_bytes > SOFT_RESPONSE_LIMIT {
462                break
463            }
464        }
465
466        let _ = response.send(Ok(cells_response));
467    }
468}
469
470/// Number of response items to reserve once the first requested item has resolved.
471///
472/// Assumes the remaining items are about as large as the first one and reserves as many as fit into
473/// [`SOFT_RESPONSE_LIMIT`], bounded by the requested `count` and `max`.
474fn response_reserve_hint(count: usize, first_len: usize, max: usize) -> usize {
475    SOFT_RESPONSE_LIMIT.div_ceil(first_len.max(1)).min(count).min(max)
476}
477
478impl<C, N> EthRequestHandler<C, N>
479where
480    N: NetworkPrimitives,
481    C: BalProvider,
482{
483    /// Handles [`GetBlockAccessLists`] queries.
484    ///
485    /// EIP-8159 defines the final `BlockAccessLists` response semantics:
486    /// <https://eips.ethereum.org/EIPS/eip-8159>
487    fn on_block_access_lists_request(
488        &self,
489        _peer_id: PeerId,
490        mut request: GetBlockAccessLists,
491        response: oneshot::Sender<RequestResult<BlockAccessLists>>,
492    ) {
493        self.metrics.eth_block_access_lists_requests_received_total.increment(1);
494        request.0.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
495
496        let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(SOFT_RESPONSE_LIMIT);
497        let access_lists =
498            self.client.get_bals_by_hashes_with_limit(&request.0, limit).unwrap_or_default();
499        let _ = response.send(Ok(BlockAccessLists(access_lists)));
500    }
501}
502
503impl<C, N> EthRequestHandler<C, N>
504where
505    N: NetworkPrimitives,
506    C: BalProvider + StateProviderFactory + StateRangeProviderFactory + Clone + 'static,
507{
508    /// Handles `snap/2` (EIP-8189) requests.
509    ///
510    /// `GetAccountRange`/`GetStorageRanges` are hash-native throughout and served from retained
511    /// canonical roots via [`StateRangeProviderFactory`], on blocking tasks so that rewinding an
512    /// old root doesn't stall other requests. `GetByteCodes` is content-addressed and
513    /// independent of any particular state root, so it's served directly.
514    /// `GetBlockAccessLists` is answered from the same [`BalProvider`] store eth71's
515    /// `GetBlockAccessLists` uses, since both serve the same underlying data.
516    fn on_snap_request(
517        &mut self,
518        peer_id: PeerId,
519        request: SnapProtocolMessage,
520        response: oneshot::Sender<RequestResult<SnapResponse>>,
521    ) {
522        self.metrics.snap_requests_received_total.increment(1);
523
524        let result = match request {
525            SnapProtocolMessage::GetAccountRange(req) => {
526                return self.on_state_range_request(
527                    peer_id,
528                    StateRangeRequest::Account(req),
529                    response,
530                )
531            }
532            SnapProtocolMessage::GetStorageRanges(mut req) => {
533                // Accounts past the serving limit are never read, so a queued request releases
534                // them along with the allocation the peer sized.
535                req.account_hashes.truncate(MAX_STORAGE_RANGE_ACCOUNTS_SERVE);
536                req.account_hashes.shrink_to_fit();
537                return self.on_state_range_request(
538                    peer_id,
539                    StateRangeRequest::Storage(req),
540                    response,
541                )
542            }
543            SnapProtocolMessage::GetByteCodes(req) => {
544                let codes = self
545                    .get_byte_codes_response(&req.hashes, req.response_bytes as usize)
546                    .unwrap_or_else(|error| {
547                        tracing::debug!(target: "net::snap", %error, "failed to serve bytecodes");
548                        Vec::new()
549                    });
550                Ok(SnapResponse::ByteCodes(ByteCodesMessage { request_id: req.request_id, codes }))
551            }
552            SnapProtocolMessage::GetBlockAccessLists(mut req) => {
553                req.block_hashes.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
554                let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(
555                    (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT),
556                );
557                let block_access_lists = self
558                    .client
559                    .get_bals_by_hashes_with_limit(&req.block_hashes, limit)
560                    .unwrap_or_default();
561                Ok(SnapResponse::BlockAccessLists(BlockAccessListsMessage {
562                    request_id: req.request_id,
563                    block_access_lists: BlockAccessLists(block_access_lists),
564                }))
565            }
566            // The peer sent us a response-shaped message instead of a request; not something we
567            // asked for.
568            _ => Err(RequestError::BadResponse),
569        };
570
571        let _ = response.send(result);
572    }
573
574    /// Hands a `snap/2` range request to [`StateRangeRequests`].
575    fn on_state_range_request(
576        &mut self,
577        peer_id: PeerId,
578        request: StateRangeRequest,
579        response: oneshot::Sender<RequestResult<SnapResponse>>,
580    ) {
581        let incoming =
582            IncomingStateRangeRequest { peer_id, request, response, received: Instant::now() };
583        self.state_range_requests.admit(incoming, &self.client, &self.metrics);
584    }
585
586    /// Returns the bytecode for each of `hashes`, skipping hashes with no known code, stopping
587    /// once `response_bytes` (capped at [`SOFT_RESPONSE_LIMIT`]) is exceeded.
588    fn get_byte_codes_response(
589        &self,
590        hashes: &[B256],
591        response_bytes: usize,
592    ) -> ProviderResult<Vec<Bytes>> {
593        let state = self.client.latest()?;
594        let response_bytes = response_bytes.min(SOFT_RESPONSE_LIMIT);
595
596        let mut codes = Vec::new();
597        let mut total_bytes = 0;
598        for hash in hashes.iter().take(MAX_BYTE_CODES_SERVE) {
599            let bytes = if *hash == KECCAK_EMPTY {
600                Bytes::new()
601            } else {
602                match state.bytecode_by_hash(hash)? {
603                    Some(bytecode) => bytecode.original_bytes(),
604                    None => continue,
605                }
606            };
607            total_bytes += bytes.len();
608            codes.push(bytes);
609
610            if total_bytes > response_bytes {
611                break
612            }
613        }
614        Ok(codes)
615    }
616}
617
618/// Boundary-proof keys for a range reply: `origin`, plus the last returned item's key if any.
619fn boundary_proof_keys<T>(origin: B256, last: Option<&(B256, T)>) -> Vec<B256> {
620    match last {
621        Some((last, _)) => vec![origin, *last],
622        None => vec![origin],
623    }
624}
625
626/// An endless future.
627///
628/// This should be spawned or used as part of `tokio::select!`.
629impl<C, N> Future for EthRequestHandler<C, N>
630where
631    N: NetworkPrimitives,
632    C: BalProvider
633        + StateProviderFactory
634        + StateRangeProviderFactory
635        + BlockReader<Block = N::Block, Receipt = N::Receipt>
636        + HeaderProvider<Header = N::BlockHeader>
637        + Clone
638        + Unpin
639        + 'static,
640{
641    type Output = ();
642
643    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
644        let this = self.get_mut();
645
646        // Frees the slots of finished state range tasks first, so the requests admitted below see
647        // them and queued requests keep their turn.
648        this.state_range_requests.poll(cx, &this.client, &this.metrics);
649
650        let mut acc = Duration::ZERO;
651        let maybe_more_incoming_requests = metered_poll_nested_stream_with_budget!(
652            acc,
653            "net::eth",
654            "Incoming eth requests stream",
655            DEFAULT_BUDGET_TRY_DRAIN_DOWNLOADERS,
656            this.incoming_requests.poll_next_unpin(cx),
657            |incoming| {
658                match incoming {
659                    IncomingEthRequest::GetBlockHeaders { peer_id, request, response } => {
660                        this.on_headers_request(peer_id, request, response)
661                    }
662                    IncomingEthRequest::GetBlockBodies { peer_id, request, response } => {
663                        this.on_bodies_request(peer_id, request, response)
664                    }
665                    IncomingEthRequest::GetNodeData { peer_id, request, response } => {
666                        this.on_node_data_request(peer_id, request, response)
667                    }
668                    IncomingEthRequest::GetReceipts { peer_id, request, response } => {
669                        this.on_receipts_request(peer_id, request, response)
670                    }
671                    IncomingEthRequest::GetReceipts69 { peer_id, request, response } => {
672                        this.on_receipts69_request(peer_id, request, response)
673                    }
674                    IncomingEthRequest::GetReceipts70 { peer_id, request, response } => {
675                        this.on_receipts70_request(peer_id, request, response)
676                    }
677                    IncomingEthRequest::GetBlockAccessLists { peer_id, request, response } => {
678                        this.on_block_access_lists_request(peer_id, request, response)
679                    }
680                    IncomingEthRequest::GetCells { peer_id, request, response } => {
681                        this.on_cells_request(peer_id, request, response)
682                    }
683                    IncomingEthRequest::GetSnap { peer_id, request, response } => {
684                        this.on_snap_request(peer_id, request, response)
685                    }
686                }
687            },
688        );
689
690        this.metrics.acc_duration_poll_eth_req_handler.set(acc.as_secs_f64());
691
692        // Polled again so that the tasks and the queue timeout of requests admitted above wake this
693        // handler.
694        this.state_range_requests.poll(cx, &this.client, &this.metrics);
695
696        // stream is fully drained and import futures pending
697        if maybe_more_incoming_requests {
698            // make sure we're woken up again
699            cx.waker().wake_by_ref();
700        }
701
702        Poll::Pending
703    }
704}
705
706/// `snap/2` state range requests served off the handler task.
707///
708/// Unlike other requests, serving a range for an old root rewinds the state to that block, which
709/// can take seconds and would stall every peer if it ran on the handler task.
710///
711/// At most [`MAX_CONCURRENT_STATE_RANGE_REQUESTS`] run at once, one per peer. A request that can't
712/// start waits in a queue of at most [`MAX_QUEUED_STATE_RANGE_REQUESTS`], one place per peer, for
713/// at most [`STATE_RANGE_REQUEST_QUEUE_TIMEOUT`].
714#[derive(Debug, Default)]
715struct StateRangeRequests {
716    /// Requests being served on blocking tasks, with the peer each belongs to.
717    tasks: Vec<(PeerId, JoinHandle<()>)>,
718    /// Requests waiting for a slot in `tasks`, oldest first.
719    queue: VecDeque<IncomingStateRangeRequest>,
720    /// Wakes the handler when the oldest queued request times out.
721    queue_timeout: Option<Pin<Box<Sleep>>>,
722}
723
724impl StateRangeRequests {
725    /// Starts `incoming` or queues it until a slot is free.
726    ///
727    /// A request whose peer has already gone is dropped. One that finds the queue full, or its
728    /// peer already holding a place in it, is answered empty.
729    fn admit<C>(
730        &mut self,
731        incoming: IncomingStateRangeRequest,
732        client: &C,
733        metrics: &EthRequestHandlerMetrics,
734    ) where
735        C: StateRangeProviderFactory + Clone + Send + 'static,
736    {
737        if incoming.response.is_closed() {
738            return
739        }
740        if self.can_start(incoming.peer_id) {
741            return self.spawn(incoming, client)
742        }
743
744        self.remove_stale(metrics);
745        if self.queue.len() >= MAX_QUEUED_STATE_RANGE_REQUESTS ||
746            self.queue.iter().any(|queued| queued.peer_id == incoming.peer_id)
747        {
748            debug!(
749                target: "net::snap",
750                peer_id=?incoming.peer_id,
751                queued=self.queue.len(),
752                "no room to queue state range request"
753            );
754            return incoming.reply_empty(metrics)
755        }
756        self.queue.push_back(incoming);
757    }
758
759    /// Returns whether a request from `peer_id` can start now.
760    fn can_start(&self, peer_id: PeerId) -> bool {
761        self.tasks.len() < MAX_CONCURRENT_STATE_RANGE_REQUESTS &&
762            self.tasks.iter().all(|(running, _)| *running != peer_id)
763    }
764
765    /// Serves `incoming` on a blocking task, which holds its peer's slot until it finishes.
766    fn spawn<C>(&mut self, incoming: IncomingStateRangeRequest, client: &C)
767    where
768        C: StateRangeProviderFactory + Clone + Send + 'static,
769    {
770        let IncomingStateRangeRequest { peer_id, request, response, .. } = incoming;
771        let client = client.clone();
772        let span = Span::current();
773        let task = tokio::task::spawn_blocking(move || {
774            let _guard = span.enter();
775            let empty = request.empty_response();
776            let result = request.serve(&client).unwrap_or_else(|error| {
777                debug!(target: "net::snap", %error, "failed to serve state range");
778                empty
779            });
780            let _ = response.send(Ok(result));
781        });
782        self.tasks.push((peer_id, task));
783    }
784
785    /// Drops queued requests whose peer has gone, and answers empty those that waited longer than
786    /// [`STATE_RANGE_REQUEST_QUEUE_TIMEOUT`].
787    fn remove_stale(&mut self, metrics: &EthRequestHandlerMetrics) {
788        let mut index = 0;
789        while index < self.queue.len() {
790            let queued = &self.queue[index];
791            if queued.response.is_closed() {
792                self.queue.remove(index);
793            } else if queued.received.elapsed() < STATE_RANGE_REQUEST_QUEUE_TIMEOUT {
794                index += 1;
795            } else if let Some(incoming) = self.queue.remove(index) {
796                debug!(
797                    target: "net::snap",
798                    peer_id=?incoming.peer_id,
799                    "state range request timed out in queue"
800                );
801                incoming.reply_empty(metrics);
802            }
803        }
804    }
805
806    /// Frees the slots of finished tasks and starts queued requests, oldest first, in their place.
807    ///
808    /// Registers `cx` to be woken when a task finishes or the oldest queued request times out.
809    fn poll<C>(&mut self, cx: &mut Context<'_>, client: &C, metrics: &EthRequestHandlerMetrics)
810    where
811        C: StateRangeProviderFactory + Clone + Send + 'static,
812    {
813        // Runs again after starting queued requests so that their tasks wake this context, and
814        // after the queue timeout fires so that it is armed for the next request.
815        loop {
816            self.remove_stale(metrics);
817            self.tasks.retain_mut(|(_, task)| match Pin::new(task).poll(cx) {
818                Poll::Pending => true,
819                Poll::Ready(result) => {
820                    if let Err(error) = result {
821                        error!(target: "net::snap", %error, "state range task failed");
822                    }
823                    false
824                }
825            });
826
827            let mut spawned = false;
828            let mut index = 0;
829            while index < self.queue.len() && self.tasks.len() < MAX_CONCURRENT_STATE_RANGE_REQUESTS
830            {
831                // A peer with a request still running keeps its place and is passed over.
832                if !self.can_start(self.queue[index].peer_id) {
833                    index += 1;
834                } else if let Some(incoming) = self.queue.remove(index) {
835                    self.spawn(incoming, client);
836                    spawned = true;
837                }
838            }
839            if !spawned && !self.poll_queue_timeout(cx) {
840                break
841            }
842        }
843    }
844
845    /// Arms the timeout of the oldest queued request, returning whether it already fired.
846    fn poll_queue_timeout(&mut self, cx: &mut Context<'_>) -> bool {
847        let Some(oldest) = self.queue.front() else {
848            self.queue_timeout = None;
849            return false
850        };
851        let deadline = oldest.received + STATE_RANGE_REQUEST_QUEUE_TIMEOUT;
852        let timeout = self.queue_timeout.get_or_insert_with(|| Box::pin(sleep_until(deadline)));
853        timeout.as_mut().reset(deadline);
854        timeout.as_mut().poll(cx).is_ready()
855    }
856}
857
858/// A `snap/2` range request waiting for, or holding, a serving slot.
859#[derive(Debug)]
860struct IncomingStateRangeRequest {
861    /// Peer that sent the request.
862    peer_id: PeerId,
863    /// The range request the peer sent.
864    request: StateRangeRequest,
865    /// Channel the response is sent on.
866    response: oneshot::Sender<RequestResult<SnapResponse>>,
867    /// When the request arrived, which bounds how long it may wait in the queue.
868    received: Instant,
869}
870
871impl IncomingStateRangeRequest {
872    /// Answers the request empty without serving it.
873    fn reply_empty(self, metrics: &EthRequestHandlerMetrics) {
874        metrics.snap_state_range_requests_unserved_total.increment(1);
875        let _ = self.response.send(Ok(self.request.empty_response()));
876    }
877}
878
879/// A `snap/2` request for a range of state at a given root.
880#[derive(Debug)]
881enum StateRangeRequest {
882    /// A `GetAccountRange` request.
883    Account(GetAccountRangeMessage),
884    /// A `GetStorageRanges` request.
885    Storage(GetStorageRangesMessage),
886}
887
888impl StateRangeRequest {
889    /// Returns the response for a request that isn't served, which snap clients read as the root
890    /// being unavailable.
891    const fn empty_response(&self) -> SnapResponse {
892        match self {
893            Self::Account(req) => SnapResponse::AccountRange(AccountRangeMessage {
894                request_id: req.request_id,
895                accounts: Vec::new(),
896                proof: Vec::new(),
897            }),
898            Self::Storage(req) => SnapResponse::StorageRanges(StorageRangesMessage {
899                request_id: req.request_id,
900                slots: Vec::new(),
901                proof: Vec::new(),
902            }),
903        }
904    }
905
906    /// Serves the range from `client`.
907    fn serve(self, client: &impl StateRangeProviderFactory) -> ProviderResult<SnapResponse> {
908        match self {
909            Self::Account(req) => {
910                Self::serve_account_range(client, req).map(SnapResponse::AccountRange)
911            }
912            Self::Storage(req) => {
913                Self::serve_storage_ranges(client, req).map(SnapResponse::StorageRanges)
914            }
915        }
916    }
917
918    /// Serves a `GetAccountRange` request via [`StateRangeProviderFactory`].
919    ///
920    /// Fails the request if a storage root or proof becomes unavailable after the range lookup.
921    /// Proves the boundary between `starting_hash` and the last returned account, per snap/2's
922    /// boundary-proof requirement, unless the range is a zero-origin range that exhausted the
923    /// trie, in which case no proof is needed.
924    fn serve_account_range(
925        client: &impl StateRangeProviderFactory,
926        req: GetAccountRangeMessage,
927    ) -> ProviderResult<AccountRangeMessage> {
928        let empty = AccountRangeMessage {
929            request_id: req.request_id,
930            accounts: Vec::new(),
931            proof: Vec::new(),
932        };
933
934        let response_bytes = (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT);
935        let Some(state) = client.state_range_provider(req.root_hash)? else { return Ok(empty) };
936        let RangeResponse { items: accounts, end } =
937            state.account_range(req.starting_hash, req.limit_hash, response_bytes)?;
938
939        let proof = if req.starting_hash.is_zero() && end == RangeEnd::Exhausted {
940            Vec::new()
941        } else {
942            let boundary_keys = boundary_proof_keys(req.starting_hash, accounts.last());
943            state.account_range_proof(&boundary_keys)?
944        };
945
946        let mut account_data = Vec::with_capacity(accounts.len());
947        for (hash, account) in accounts {
948            let storage_root = state.storage_root_by_hash(hash)?;
949            account_data.push(AccountData::from_trie_account(
950                hash,
951                &account.into_trie_account(storage_root),
952            ));
953        }
954
955        Ok(AccountRangeMessage { request_id: req.request_id, accounts: account_data, proof })
956    }
957
958    /// Serves a `GetStorageRanges` request via [`StateRangeProviderFactory`].
959    ///
960    /// `starting_hash`/`limit_hash` apply only to the first account. An account unavailable at
961    /// this root makes the whole response empty, rather than skipping it and shifting later
962    /// accounts' positions. A proof stops the response at the first account that isn't a
963    /// complete, zero-origin range.
964    fn serve_storage_ranges(
965        client: &impl StateRangeProviderFactory,
966        req: GetStorageRangesMessage,
967    ) -> ProviderResult<StorageRangesMessage> {
968        let empty = StorageRangesMessage {
969            request_id: req.request_id,
970            slots: Vec::new(),
971            proof: Vec::new(),
972        };
973        let Some(state) = client.state_range_provider(req.root_hash)? else { return Ok(empty) };
974        let mut slots: Vec<Vec<StorageData>> = Vec::new();
975        let mut proof = Vec::new();
976        let mut remaining_bytes = (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT);
977
978        for (i, &hashed_address) in
979            req.account_hashes.iter().take(MAX_STORAGE_RANGE_ACCOUNTS_SERVE).enumerate()
980        {
981            // Keeps traversing storage-empty accounts under a zero budget so a real slot still
982            // gets served; worst case is a lookup per account, bounded by the take() above.
983            if remaining_bytes == 0 && slots.iter().any(|range| !range.is_empty()) {
984                break
985            }
986            let (origin, limit) = if i == 0 {
987                (
988                    req.starting_hash.unwrap_or(B256::ZERO),
989                    req.limit_hash.unwrap_or(B256::repeat_byte(0xff)),
990                )
991            } else {
992                (B256::ZERO, B256::repeat_byte(0xff))
993            };
994            let Some(RangeResponse { items: account_slots, end }) =
995                state.storage_range(hashed_address, origin, limit, remaining_bytes)?
996            else {
997                return Ok(empty)
998            };
999
1000            remaining_bytes = remaining_bytes.saturating_sub(account_slots.len() * 64);
1001            let last = account_slots.last().map(|(hash, _)| *hash);
1002            let needs_proof = !origin.is_zero() || end != RangeEnd::Exhausted;
1003            slots.push(
1004                account_slots
1005                    .into_iter()
1006                    // snap clients verify proofs against RLP-encoded storage trie leaves.
1007                    .map(|(hash, value)| StorageData::from_value(hash, value))
1008                    .collect(),
1009            );
1010
1011            if needs_proof {
1012                let boundary_keys = match last {
1013                    Some(last) => vec![origin, last],
1014                    None => vec![origin],
1015                };
1016                proof = state.storage_range_proof(hashed_address, &boundary_keys)?;
1017                break
1018            }
1019        }
1020
1021        Ok(StorageRangesMessage { request_id: req.request_id, slots, proof })
1022    }
1023}
1024
1025/// All `eth` request related to blocks delegated by the network.
1026#[derive(Debug)]
1027pub enum IncomingEthRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
1028    /// Request Block headers from the peer.
1029    ///
1030    /// The response should be sent through the channel.
1031    GetBlockHeaders {
1032        /// The ID of the peer to request block headers from.
1033        peer_id: PeerId,
1034        /// The specific block headers requested.
1035        request: GetBlockHeaders,
1036        /// The channel sender for the response containing block headers.
1037        response: oneshot::Sender<RequestResult<BlockHeaders<N::BlockHeader>>>,
1038    },
1039    /// Request Block bodies from the peer.
1040    ///
1041    /// The response should be sent through the channel.
1042    GetBlockBodies {
1043        /// The ID of the peer to request block bodies from.
1044        peer_id: PeerId,
1045        /// The specific block bodies requested.
1046        request: GetBlockBodies,
1047        /// The channel sender for the response containing block bodies.
1048        response: oneshot::Sender<RequestResult<BlockBodies<N::BlockBody>>>,
1049    },
1050    /// Request Node Data from the peer.
1051    ///
1052    /// The response should be sent through the channel.
1053    GetNodeData {
1054        /// The ID of the peer to request node data from.
1055        peer_id: PeerId,
1056        /// The specific node data requested.
1057        request: GetNodeData,
1058        /// The channel sender for the response containing node data.
1059        response: oneshot::Sender<RequestResult<NodeData>>,
1060    },
1061    /// Request Receipts from the peer.
1062    ///
1063    /// The response should be sent through the channel.
1064    GetReceipts {
1065        /// The ID of the peer to request receipts from.
1066        peer_id: PeerId,
1067        /// The specific receipts requested.
1068        request: GetReceipts,
1069        /// The channel sender for the response containing receipts.
1070        response: oneshot::Sender<RequestResult<Receipts<N::Receipt>>>,
1071    },
1072    /// Request Receipts from the peer without bloom filter.
1073    ///
1074    /// The response should be sent through the channel.
1075    GetReceipts69 {
1076        /// The ID of the peer to request receipts from.
1077        peer_id: PeerId,
1078        /// The specific receipts requested.
1079        request: GetReceipts,
1080        /// The channel sender for the response containing Receipts69.
1081        response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
1082    },
1083    /// Request Receipts from the peer using eth/70.
1084    ///
1085    /// The response should be sent through the channel.
1086    GetReceipts70 {
1087        /// The ID of the peer to request receipts from.
1088        peer_id: PeerId,
1089        /// The specific receipts requested including the `firstBlockReceiptIndex`.
1090        request: GetReceipts70,
1091        /// The channel sender for the response containing Receipts70.
1092        response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
1093    },
1094    /// Request Block Access Lists from the peer.
1095    ///
1096    /// The response should be sent through the channel.
1097    GetBlockAccessLists {
1098        /// The ID of the peer to request block access lists from.
1099        peer_id: PeerId,
1100        /// The requested block hashes.
1101        request: GetBlockAccessLists,
1102        /// The channel sender for the response containing block access lists.
1103        response: oneshot::Sender<RequestResult<BlockAccessLists>>,
1104    },
1105    /// Request Cells from the peer.
1106    ///
1107    /// The response should be sent through the channel.
1108    GetCells {
1109        /// The ID of the peer to request cells from.
1110        peer_id: PeerId,
1111        /// The requested block hashes.
1112        request: GetCells,
1113        /// The channel sender for the response containing cells.
1114        response: oneshot::Sender<RequestResult<Cells>>,
1115    },
1116    /// Request a `snap/2` message from the peer.
1117    ///
1118    /// The response should be sent through the channel.
1119    GetSnap {
1120        /// The ID of the peer to request from.
1121        peer_id: PeerId,
1122        /// The `snap/2` request.
1123        request: SnapProtocolMessage,
1124        /// The channel sender for the response.
1125        response: oneshot::Sender<RequestResult<SnapResponse>>,
1126    },
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131    use super::*;
1132    use alloy_consensus::{constants::EMPTY_ROOT_HASH, Header, TxType};
1133    use alloy_eips::{
1134        eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
1135        eip7594::{BlobCellMask, BlobTransactionSidecarVariant, Cell},
1136    };
1137    use alloy_primitives::{keccak256, Address, Log, LogData, TxHash, B128, U256};
1138    use alloy_rlp::{Decodable, Encodable};
1139    use reth_eth_wire::{message::RequestPair, EthMessage, EthStreamInner, EthVersion};
1140    use reth_ethereum_primitives::Receipt;
1141    use reth_primitives_traits::Account;
1142    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1143    use reth_storage_api::noop::NoopProvider;
1144    use reth_transaction_pool::blobstore::{
1145        BlobStoreCleanupStat, BlobStoreError, PooledBlobSidecar,
1146    };
1147    use std::sync::{
1148        atomic::{AtomicUsize, Ordering},
1149        Arc,
1150    };
1151    use test_case::test_case;
1152    use tokio::sync::mpsc;
1153
1154    #[derive(Debug, Default)]
1155    struct CountingBlobStore {
1156        get_cells_calls: Arc<AtomicUsize>,
1157        expected_cell_mask: Option<BlobCellMask>,
1158    }
1159
1160    impl BlobStore for CountingBlobStore {
1161        fn insert(&self, _tx: B256, _data: PooledBlobSidecar) -> Result<(), BlobStoreError> {
1162            Ok(())
1163        }
1164
1165        fn insert_all(&self, _txs: Vec<(B256, PooledBlobSidecar)>) -> Result<(), BlobStoreError> {
1166            Ok(())
1167        }
1168
1169        fn delete(&self, _tx: B256) -> Result<(), BlobStoreError> {
1170            Ok(())
1171        }
1172
1173        fn delete_all(&self, _txs: Vec<B256>) -> Result<(), BlobStoreError> {
1174            Ok(())
1175        }
1176
1177        fn cleanup(&self) -> BlobStoreCleanupStat {
1178            BlobStoreCleanupStat::default()
1179        }
1180
1181        fn get(
1182            &self,
1183            _tx: B256,
1184        ) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError> {
1185            Ok(None)
1186        }
1187
1188        fn contains(&self, _tx: B256) -> Result<bool, BlobStoreError> {
1189            Ok(false)
1190        }
1191
1192        fn get_all(
1193            &self,
1194            _txs: Vec<B256>,
1195        ) -> Result<Vec<(B256, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError> {
1196            Ok(vec![])
1197        }
1198
1199        fn get_exact(
1200            &self,
1201            txs: Vec<B256>,
1202        ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError> {
1203            if txs.is_empty() {
1204                return Ok(vec![])
1205            }
1206
1207            Err(BlobStoreError::MissingSidecar(txs[0]))
1208        }
1209
1210        fn get_by_versioned_hashes_v1(
1211            &self,
1212            versioned_hashes: &[B256],
1213        ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError> {
1214            Ok(vec![None; versioned_hashes.len()])
1215        }
1216
1217        fn get_by_versioned_hashes_v2(
1218            &self,
1219            _versioned_hashes: &[B256],
1220        ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError> {
1221            Ok(None)
1222        }
1223
1224        fn get_by_versioned_hashes_v3(
1225            &self,
1226            versioned_hashes: &[B256],
1227        ) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError> {
1228            Ok(vec![None; versioned_hashes.len()])
1229        }
1230
1231        fn get_by_versioned_hashes_v4(
1232            &self,
1233            versioned_hashes: &[B256],
1234            _cell_mask: BlobCellMask,
1235        ) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
1236            Ok(vec![None; versioned_hashes.len()])
1237        }
1238
1239        fn has_versioned_hashes(
1240            &self,
1241            versioned_hashes: &[B256],
1242        ) -> Result<Vec<bool>, BlobStoreError> {
1243            Ok(vec![false; versioned_hashes.len()])
1244        }
1245
1246        fn get_cells(
1247            &self,
1248            _tx_hash: TxHash,
1249            cell_mask: BlobCellMask,
1250        ) -> Result<Option<Vec<Cell>>, BlobStoreError> {
1251            self.get_cells_calls.fetch_add(1, Ordering::Relaxed);
1252            if let Some(expected) = self.expected_cell_mask {
1253                assert_eq!(cell_mask, expected);
1254                return Ok(Some(vec![Cell::default()]))
1255            }
1256            Ok(None)
1257        }
1258
1259        fn data_size_hint(&self) -> Option<usize> {
1260            Some(0)
1261        }
1262
1263        fn blobs_len(&self) -> usize {
1264            0
1265        }
1266    }
1267
1268    #[tokio::test]
1269    async fn get_cells_request_limits_blob_store_lookups() {
1270        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1271        let get_cells_calls = Arc::new(AtomicUsize::new(0));
1272        let blob_store = CountingBlobStore {
1273            get_cells_calls: Arc::clone(&get_cells_calls),
1274            ..Default::default()
1275        };
1276        let handler = EthRequestHandler::<NoopProvider>::new(NoopProvider::default(), incoming_rx)
1277            .with_blob_store(Box::new(blob_store));
1278        let (response, rx) = oneshot::channel();
1279        let request =
1280            GetCells { hashes: vec![B256::ZERO; MAX_CELLS_SERVE + 1], cell_mask: B128::default() };
1281
1282        handler.on_cells_request(PeerId::default(), request, response);
1283
1284        let cells = rx.await.unwrap().unwrap();
1285        assert!(cells.hashes.is_empty());
1286        assert_eq!(get_cells_calls.load(Ordering::Relaxed), MAX_CELLS_SERVE);
1287    }
1288
1289    #[test_case(0)]
1290    #[test_case(7)]
1291    #[test_case(8)]
1292    #[test_case(63)]
1293    #[test_case(64)]
1294    #[test_case(127)]
1295    #[tokio::test]
1296    async fn get_cells_request_uses_little_endian_wire_mask(index: u32) {
1297        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1298        let numeric_mask = 1u128 << index;
1299        let blob_store = CountingBlobStore {
1300            expected_cell_mask: Some(BlobCellMask::from_bits(numeric_mask)),
1301            ..Default::default()
1302        };
1303        let handler = EthRequestHandler::<NoopProvider>::new(NoopProvider::default(), incoming_rx)
1304            .with_blob_store(Box::new(blob_store));
1305        let wire_mask = B128::from(numeric_mask.to_le_bytes());
1306        let request = GetCells { hashes: vec![B256::ZERO], cell_mask: wire_mask };
1307        let mut encoded = Vec::new();
1308        request.encode(&mut encoded);
1309        let request = GetCells::decode(&mut encoded.as_slice()).unwrap();
1310        let (response, rx) = oneshot::channel();
1311
1312        handler.on_cells_request(PeerId::default(), request, response);
1313
1314        let cells = rx.await.unwrap().unwrap();
1315        assert_eq!(cells.hashes, vec![B256::ZERO]);
1316        assert_eq!(cells.cells, vec![vec![Cell::default()]]);
1317        assert_eq!(cells.cell_mask, wire_mask);
1318        encoded.clear();
1319        cells.encode(&mut encoded);
1320        assert_eq!(Cells::decode(&mut encoded.as_slice()).unwrap(), cells);
1321    }
1322
1323    #[tokio::test]
1324    async fn get_node_data_responds_with_empty_payload() {
1325        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1326        let handler = EthRequestHandler::<NoopProvider>::new(NoopProvider::default(), incoming_rx);
1327        let (response, rx) = oneshot::channel();
1328
1329        handler.on_node_data_request(PeerId::default(), GetNodeData(vec![B256::ZERO]), response);
1330
1331        let node_data = rx.await.expect("response channel must not be dropped").unwrap();
1332        assert!(node_data.0.is_empty(), "GetNodeData should reply with empty NodeData");
1333    }
1334
1335    /// Creates a request handler backed by the mock provider for snap response tests.
1336    fn snap_handler(
1337        provider: MockEthProvider,
1338    ) -> EthRequestHandler<MockEthProvider, EthNetworkPrimitives> {
1339        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1340        EthRequestHandler::new(provider, incoming_rx)
1341    }
1342
1343    #[test_case(
1344        SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1345            request_id: 1,
1346            root_hash: B256::ZERO,
1347            starting_hash: B256::ZERO,
1348            limit_hash: B256::repeat_byte(0xff),
1349            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1350        }),
1351        SnapResponse::AccountRange(AccountRangeMessage {
1352            request_id: 1,
1353            accounts: Vec::new(),
1354            proof: Vec::new(),
1355        }); "account range"
1356    )]
1357    #[test_case(
1358        SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1359            request_id: 2,
1360            root_hash: B256::ZERO,
1361            account_hashes: vec![B256::ZERO],
1362            starting_hash: B256::ZERO.into(),
1363            limit_hash: B256::repeat_byte(0xff).into(),
1364            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1365        }),
1366        SnapResponse::StorageRanges(StorageRangesMessage {
1367            request_id: 2,
1368            slots: Vec::new(),
1369            proof: Vec::new(),
1370        }); "storage ranges"
1371    )]
1372    #[test_case(
1373        SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1374            request_id: 3,
1375            hashes: vec![B256::repeat_byte(0x11)],
1376            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1377        }),
1378        SnapResponse::ByteCodes(ByteCodesMessage { request_id: 3, codes: Vec::new() }); "bytecodes"
1379    )]
1380    #[tokio::test]
1381    async fn snap_requests_return_empty_responses_on_provider_errors(
1382        request: SnapProtocolMessage,
1383        expected: SnapResponse,
1384    ) {
1385        let provider = MockEthProvider::default();
1386        provider.set_snap_state_reads_fail(true);
1387        let mut handler = snap_handler(provider);
1388        let (response, rx) = oneshot::channel();
1389
1390        handler.on_snap_request(PeerId::default(), request, response);
1391
1392        assert_eq!(rx.await.unwrap(), Ok(expected));
1393    }
1394
1395    #[tokio::test]
1396    async fn unavailable_snap_state_returns_empty_response() {
1397        let provider = MockEthProvider::default();
1398        let mut handler = snap_handler(provider.clone());
1399        let (response, rx) = oneshot::channel();
1400        handler.on_snap_request(
1401            PeerId::default(),
1402            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1403                request_id: 1,
1404                root_hash: B256::ZERO,
1405                starting_hash: B256::ZERO,
1406                limit_hash: B256::repeat_byte(0xff),
1407                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1408            }),
1409            response,
1410        );
1411
1412        assert_eq!(
1413            rx.await.unwrap(),
1414            Ok(SnapResponse::AccountRange(AccountRangeMessage {
1415                request_id: 1,
1416                accounts: Vec::new(),
1417                proof: Vec::new(),
1418            }))
1419        );
1420        assert_eq!(provider.snap_state_range_resolutions(), 1);
1421    }
1422
1423    fn account_range_request() -> SnapProtocolMessage {
1424        SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1425            request_id: 1,
1426            root_hash: B256::ZERO,
1427            starting_hash: B256::ZERO,
1428            limit_hash: B256::repeat_byte(0xff),
1429            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1430        })
1431    }
1432
1433    fn empty_account_range() -> SnapResponse {
1434        SnapResponse::AccountRange(AccountRangeMessage {
1435            request_id: 1,
1436            accounts: Vec::new(),
1437            proof: Vec::new(),
1438        })
1439    }
1440
1441    #[tokio::test]
1442    async fn busy_state_range_request_waits_for_a_slot() {
1443        let provider = MockEthProvider::default();
1444        let mut handler = snap_handler(provider.clone());
1445        // Every slot is taken, and the requesting peer holds the one that finishes on `release`.
1446        let peer_id = PeerId::default();
1447        let (release, released) = oneshot::channel::<()>();
1448        handler.state_range_requests.tasks.push((
1449            peer_id,
1450            tokio::spawn(async move {
1451                let _ = released.await;
1452            }),
1453        ));
1454        let busy_peer = PeerId::repeat_byte(1);
1455        for peer in std::iter::once(busy_peer).chain((2..).map(PeerId::repeat_byte)) {
1456            if handler.state_range_requests.tasks.len() == MAX_CONCURRENT_STATE_RANGE_REQUESTS {
1457                break
1458            }
1459            handler
1460                .state_range_requests
1461                .tasks
1462                .push((peer, tokio::spawn(futures::future::pending())));
1463        }
1464        // A peer whose request keeps running is queued first, and must be passed over.
1465        let (response, mut passed_over) = oneshot::channel();
1466        handler.on_snap_request(busy_peer, account_range_request(), response);
1467        let (response, mut rx) = oneshot::channel();
1468        handler.on_snap_request(peer_id, account_range_request(), response);
1469
1470        assert_eq!(handler.state_range_requests.queue.len(), 2);
1471        assert_eq!(provider.snap_state_range_resolutions(), 0);
1472
1473        release.send(()).unwrap();
1474        // Only the queue is polled, so the request it starts is served and its slot freed only if
1475        // that task wakes this one.
1476        let EthRequestHandler { state_range_requests, client, metrics, .. } = &mut handler;
1477        let wait = Duration::from_secs(5);
1478        let started = Instant::now();
1479        let mut served = None;
1480        tokio::time::timeout(
1481            wait,
1482            std::future::poll_fn(|cx| {
1483                state_range_requests.poll(cx, client, metrics);
1484                if served.is_none() {
1485                    served = rx.try_recv().ok();
1486                }
1487                if served.is_some() &&
1488                    state_range_requests.tasks.len() < MAX_CONCURRENT_STATE_RANGE_REQUESTS
1489                {
1490                    Poll::Ready(())
1491                } else {
1492                    Poll::Pending
1493                }
1494            }),
1495        )
1496        .await
1497        .expect("queued state range request must be served once a slot frees");
1498
1499        assert!(started.elapsed() < wait, "task started from the queue must wake the poller");
1500        assert_eq!(served, Some(Ok(empty_account_range())));
1501        assert_eq!(provider.snap_state_range_resolutions(), 1);
1502        assert_eq!(handler.state_range_requests.queue.len(), 1);
1503        assert_eq!(passed_over.try_recv(), Err(oneshot::error::TryRecvError::Empty));
1504    }
1505
1506    #[tokio::test]
1507    async fn stale_queued_state_range_requests_are_not_served() {
1508        let provider = MockEthProvider::default();
1509        let mut handler = snap_handler(provider.clone());
1510        for byte in 0..MAX_CONCURRENT_STATE_RANGE_REQUESTS as u8 {
1511            handler
1512                .state_range_requests
1513                .tasks
1514                .push((PeerId::repeat_byte(byte), tokio::spawn(futures::future::pending())));
1515        }
1516        // One peer goes away while its request is queued.
1517        let (response, gone) = oneshot::channel();
1518        handler.on_snap_request(PeerId::repeat_byte(0x80), account_range_request(), response);
1519        drop(gone);
1520        // Another request is about to time out, with nothing else left to wake the handler.
1521        let SnapProtocolMessage::GetAccountRange(request) = account_range_request() else {
1522            unreachable!("fixture is an account range request")
1523        };
1524        let (response, mut expired) = oneshot::channel();
1525        handler.state_range_requests.queue.push_back(IncomingStateRangeRequest {
1526            peer_id: PeerId::repeat_byte(0x81),
1527            request: StateRangeRequest::Account(request),
1528            response,
1529            received: Instant::now() + Duration::from_millis(50) -
1530                STATE_RANGE_REQUEST_QUEUE_TIMEOUT,
1531        });
1532
1533        // Only the queue is polled, so nothing but its timeout can wake this task before the wait
1534        // below gives up.
1535        let EthRequestHandler { state_range_requests, client, metrics, .. } = &mut handler;
1536        let wait = Duration::from_secs(5);
1537        let started = Instant::now();
1538        let answer = tokio::time::timeout(
1539            wait,
1540            std::future::poll_fn(|cx| {
1541                state_range_requests.poll(cx, client, metrics);
1542                Pin::new(&mut expired).poll(cx)
1543            }),
1544        )
1545        .await
1546        .expect("queued state range request must be answered when it times out");
1547
1548        assert!(started.elapsed() < wait, "queue timeout must wake the poller");
1549        assert_eq!(answer.unwrap(), Ok(empty_account_range()));
1550        assert!(handler.state_range_requests.queue.is_empty());
1551        assert_eq!(provider.snap_state_range_resolutions(), 0);
1552    }
1553
1554    #[tokio::test]
1555    async fn incoming_state_range_request_sees_slots_freed_since_the_last_poll() {
1556        let provider = MockEthProvider::default();
1557        let (incoming_tx, incoming_rx) = mpsc::channel(1);
1558        let mut handler = snap_handler(provider.clone());
1559        handler.incoming_requests = ReceiverStream::new(incoming_rx);
1560        // The peer's task finished after the last poll, and it has another request queued.
1561        let peer_id = PeerId::default();
1562        let finished = tokio::spawn(async {});
1563        while !finished.is_finished() {
1564            tokio::task::yield_now().await;
1565        }
1566        handler.state_range_requests.tasks.push((peer_id, finished));
1567        for byte in 1..MAX_CONCURRENT_STATE_RANGE_REQUESTS as u8 {
1568            handler
1569                .state_range_requests
1570                .tasks
1571                .push((PeerId::repeat_byte(byte), tokio::spawn(futures::future::pending())));
1572        }
1573        let (response, _queued) = oneshot::channel();
1574        handler.on_snap_request(peer_id, account_range_request(), response);
1575        assert_eq!(handler.state_range_requests.queue.len(), 1);
1576
1577        // Its next request arrives before the handler has noticed the finished task.
1578        let (response, mut rx) = oneshot::channel();
1579        incoming_tx
1580            .send(IncomingEthRequest::GetSnap {
1581                peer_id,
1582                request: account_range_request(),
1583                response,
1584            })
1585            .await
1586            .unwrap();
1587        tokio::time::timeout(
1588            Duration::from_secs(5),
1589            std::future::poll_fn(|cx| {
1590                let _ = Pin::new(&mut handler).poll(cx);
1591                Pin::new(&mut rx).poll(cx)
1592            }),
1593        )
1594        .await
1595        .expect("incoming state range request must be answered")
1596        .unwrap()
1597        .unwrap();
1598
1599        // Both requests were served in turn, neither was turned away.
1600        assert_eq!(provider.snap_state_range_resolutions(), 2);
1601    }
1602
1603    #[tokio::test]
1604    async fn overflowing_state_range_requests_return_empty_responses() {
1605        let provider = MockEthProvider::default();
1606        let mut handler = snap_handler(provider.clone());
1607        for byte in 0..MAX_CONCURRENT_STATE_RANGE_REQUESTS as u8 {
1608            handler
1609                .state_range_requests
1610                .tasks
1611                .push((PeerId::repeat_byte(byte), tokio::spawn(futures::future::pending())));
1612        }
1613        let queued_peer = |index: usize| PeerId::repeat_byte(0x80 + index as u8);
1614        let (response, queued) = oneshot::channel();
1615        handler.on_snap_request(queued_peer(0), account_range_request(), response);
1616
1617        // The peer already holds a place in the queue.
1618        let (response, rx) = oneshot::channel();
1619        handler.on_snap_request(queued_peer(0), account_range_request(), response);
1620        assert_eq!(rx.await.unwrap(), Ok(empty_account_range()));
1621
1622        // The queue is full.
1623        let mut waiting = vec![queued];
1624        for index in 1..MAX_QUEUED_STATE_RANGE_REQUESTS {
1625            let (response, rx) = oneshot::channel();
1626            handler.on_snap_request(queued_peer(index), account_range_request(), response);
1627            waiting.push(rx);
1628        }
1629        let (response, rx) = oneshot::channel();
1630        handler.on_snap_request(PeerId::repeat_byte(0xff), account_range_request(), response);
1631        assert_eq!(rx.await.unwrap(), Ok(empty_account_range()));
1632
1633        // Queued requests stay unanswered until a slot frees.
1634        assert!(waiting
1635            .iter_mut()
1636            .all(|rx| rx.try_recv() == Err(oneshot::error::TryRecvError::Empty)));
1637        assert_eq!(provider.snap_state_range_resolutions(), 0);
1638    }
1639
1640    #[tokio::test]
1641    async fn state_range_request_from_gone_peer_is_dropped() {
1642        let mut handler = snap_handler(MockEthProvider::default());
1643        let (response, rx) = oneshot::channel();
1644        drop(rx);
1645
1646        handler.on_snap_request(PeerId::default(), account_range_request(), response);
1647
1648        assert!(handler.state_range_requests.tasks.is_empty());
1649    }
1650
1651    #[tokio::test]
1652    async fn finished_state_range_request_frees_its_peer_slot() {
1653        let mut handler = snap_handler(MockEthProvider::default());
1654        let (response, rx) = oneshot::channel();
1655
1656        handler.on_snap_request(PeerId::default(), account_range_request(), response);
1657
1658        assert_eq!(handler.state_range_requests.tasks.len(), 1);
1659        rx.await.unwrap().unwrap();
1660        tokio::time::timeout(
1661            Duration::from_secs(5),
1662            std::future::poll_fn(|cx| {
1663                let _ = Pin::new(&mut handler).poll(cx);
1664                if handler.state_range_requests.tasks.is_empty() {
1665                    Poll::Ready(())
1666                } else {
1667                    Poll::Pending
1668                }
1669            }),
1670        )
1671        .await
1672        .expect("finished state range task must free its peer slot");
1673    }
1674
1675    #[tokio::test]
1676    async fn snap_requests_return_empty_responses_on_inconsistent_provider_results() {
1677        let missing_storage_root = MockEthProvider::default();
1678        missing_storage_root
1679            .set_snap_account_range(vec![(B256::ZERO, Account::default())], RangeEnd::Exhausted);
1680
1681        let missing_account_proof = MockEthProvider::default();
1682        missing_account_proof.set_snap_account_range(Vec::new(), RangeEnd::Exhausted);
1683
1684        let missing_storage_proof = MockEthProvider::default();
1685        missing_storage_proof
1686            .push_snap_storage_range(vec![(B256::ZERO, U256::ONE)], RangeEnd::ByteLimit);
1687
1688        let storage_disappears = MockEthProvider::default();
1689        storage_disappears.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1690        storage_disappears.push_unavailable_snap_storage_range();
1691
1692        let account_request = || {
1693            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1694                request_id: 1,
1695                root_hash: B256::ZERO,
1696                starting_hash: B256::ZERO,
1697                limit_hash: B256::repeat_byte(0xff),
1698                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1699            })
1700        };
1701        let storage_request = |account_hashes| {
1702            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1703                request_id: 2,
1704                root_hash: B256::ZERO,
1705                account_hashes,
1706                starting_hash: B256::ZERO.into(),
1707                limit_hash: B256::repeat_byte(0xff).into(),
1708                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1709            })
1710        };
1711        let empty_accounts = SnapResponse::AccountRange(AccountRangeMessage {
1712            request_id: 1,
1713            accounts: Vec::new(),
1714            proof: Vec::new(),
1715        });
1716        let empty_storage = SnapResponse::StorageRanges(StorageRangesMessage {
1717            request_id: 2,
1718            slots: Vec::new(),
1719            proof: Vec::new(),
1720        });
1721        let cases = [
1722            (missing_storage_root, account_request(), empty_accounts.clone()),
1723            (missing_account_proof, account_request(), empty_accounts),
1724            (missing_storage_proof, storage_request(vec![B256::ZERO]), empty_storage.clone()),
1725            (storage_disappears, storage_request(vec![B256::ZERO, B256::ZERO]), empty_storage),
1726        ];
1727
1728        for (provider, request, expected) in cases {
1729            let mut handler = snap_handler(provider.clone());
1730            let (response, rx) = oneshot::channel();
1731            handler.on_snap_request(PeerId::default(), request, response);
1732            assert_eq!(rx.await.unwrap(), Ok(expected));
1733            assert_eq!(provider.snap_state_range_resolutions(), 1);
1734        }
1735    }
1736
1737    #[tokio::test]
1738    #[allow(clippy::needless_update)]
1739    async fn snap_account_range_response_encodes_accounts_and_proof() {
1740        let provider = MockEthProvider::default();
1741        let first_hash = B256::repeat_byte(0x01);
1742        let second_hash = B256::repeat_byte(0x02);
1743        let storage_root = B256::repeat_byte(0x11);
1744        let code_hash = B256::repeat_byte(0x22);
1745        let proof = vec![Bytes::from_static(&[0xaa])];
1746        provider.set_snap_account_range(
1747            vec![
1748                (
1749                    first_hash,
1750                    Account {
1751                        nonce: 1,
1752                        balance: U256::from(2),
1753                        bytecode_hash: Some(code_hash),
1754                        ..Default::default()
1755                    },
1756                ),
1757                (second_hash, Account { nonce: 3, balance: U256::from(4), ..Default::default() }),
1758            ],
1759            // A hash-limit stop (not an exhausted trie) so a boundary proof is still expected,
1760            // matching the mocked `proof` below.
1761            RangeEnd::HashLimit,
1762        );
1763        provider.set_snap_storage_root(first_hash, storage_root);
1764        provider.set_snap_storage_root(second_hash, EMPTY_ROOT_HASH);
1765        provider.set_snap_account_proof(Some(proof.clone()));
1766
1767        let mut full_body = vec![0xf8, 0x44, 0x01, 0x02, 0xa0];
1768        full_body.extend_from_slice(storage_root.as_slice());
1769        full_body.push(0xa0);
1770        full_body.extend_from_slice(code_hash.as_slice());
1771        let empty_body = Bytes::from_static(&[0xc4, 0x03, 0x04, 0x80, 0x80]);
1772
1773        let mut handler = snap_handler(provider.clone());
1774        let (response, rx) = oneshot::channel();
1775        handler.on_snap_request(
1776            PeerId::default(),
1777            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1778                request_id: 1,
1779                root_hash: B256::ZERO,
1780                starting_hash: B256::ZERO,
1781                limit_hash: B256::repeat_byte(0xff),
1782                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1783            }),
1784            response,
1785        );
1786
1787        assert_eq!(
1788            rx.await.unwrap(),
1789            Ok(SnapResponse::AccountRange(AccountRangeMessage {
1790                request_id: 1,
1791                accounts: vec![
1792                    AccountData {
1793                        hash: first_hash,
1794                        body: alloy_rlp::decode_exact(&full_body).unwrap()
1795                    },
1796                    AccountData {
1797                        hash: second_hash,
1798                        body: alloy_rlp::decode_exact(&empty_body).unwrap()
1799                    },
1800                ],
1801                proof,
1802            }))
1803        );
1804        assert_eq!(provider.snap_state_range_resolutions(), 1);
1805    }
1806
1807    #[tokio::test]
1808    async fn snap_account_range_response_skips_proof_for_exhausted_zero_origin_range() {
1809        let provider = MockEthProvider::default();
1810        let hash = B256::repeat_byte(0x01);
1811        provider.set_snap_account_range(
1812            vec![(hash, Account { nonce: 1, balance: U256::from(2), ..Default::default() })],
1813            RangeEnd::Exhausted,
1814        );
1815        provider.set_snap_storage_root(hash, EMPTY_ROOT_HASH);
1816        // A non-empty mocked proof: if the skip-proof optimization regressed to "never skip",
1817        // this wrongly-included proof would surface in the response and fail the assertion below.
1818        provider.set_snap_account_proof(Some(vec![Bytes::from_static(&[0xaa])]));
1819
1820        let mut handler = snap_handler(provider.clone());
1821        let (response, rx) = oneshot::channel();
1822        handler.on_snap_request(
1823            PeerId::default(),
1824            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1825                request_id: 1,
1826                root_hash: B256::ZERO,
1827                starting_hash: B256::ZERO,
1828                limit_hash: B256::repeat_byte(0xff),
1829                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1830            }),
1831            response,
1832        );
1833
1834        let Ok(SnapResponse::AccountRange(AccountRangeMessage { accounts, proof, .. })) =
1835            rx.await.unwrap()
1836        else {
1837            panic!("expected an account range response");
1838        };
1839        assert_eq!(accounts.len(), 1);
1840        assert!(proof.is_empty(), "a zero-origin, exhausted range must not carry a boundary proof");
1841    }
1842
1843    #[tokio::test]
1844    async fn snap_storage_range_response_encodes_values_and_proof() {
1845        let provider = MockEthProvider::default();
1846        let first_hash = B256::repeat_byte(0x01);
1847        let second_hash = B256::repeat_byte(0x02);
1848        let origin = B256::repeat_byte(0x10);
1849        let proof = vec![Bytes::from_static(&[0xbb])];
1850        provider.push_snap_storage_range(
1851            vec![(first_hash, U256::from(0x0102)), (second_hash, U256::from(0xff))],
1852            RangeEnd::Exhausted,
1853        );
1854        provider.set_snap_storage_proof(Some(proof.clone()));
1855
1856        let mut handler = snap_handler(provider.clone());
1857        let (response, rx) = oneshot::channel();
1858        handler.on_snap_request(
1859            PeerId::default(),
1860            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1861                request_id: 2,
1862                root_hash: B256::ZERO,
1863                account_hashes: vec![B256::repeat_byte(0x03)],
1864                starting_hash: origin.into(),
1865                limit_hash: B256::repeat_byte(0xff).into(),
1866                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1867            }),
1868            response,
1869        );
1870
1871        assert_eq!(
1872            rx.await.unwrap(),
1873            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1874                request_id: 2,
1875                slots: vec![vec![
1876                    StorageData { hash: first_hash, data: Bytes::from_static(&[0x82, 0x01, 0x02]) },
1877                    StorageData { hash: second_hash, data: Bytes::from_static(&[0x81, 0xff]) },
1878                ]],
1879                proof,
1880            }))
1881        );
1882        assert_eq!(provider.snap_storage_range_requests()[0].1, origin);
1883        assert_eq!(provider.snap_state_range_resolutions(), 1);
1884    }
1885
1886    #[tokio::test]
1887    async fn snap_storage_ranges_only_bound_the_first_account() {
1888        let provider = MockEthProvider::default();
1889        provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1890        provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1891        let first_account = B256::repeat_byte(0x01);
1892        let second_account = B256::repeat_byte(0x02);
1893        let origin = B256::ZERO;
1894        let limit = B256::repeat_byte(0x22);
1895
1896        let mut handler = snap_handler(provider.clone());
1897        let (response, rx) = oneshot::channel();
1898        handler.on_snap_request(
1899            PeerId::default(),
1900            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1901                request_id: 3,
1902                root_hash: B256::ZERO,
1903                account_hashes: vec![first_account, second_account],
1904                starting_hash: origin.into(),
1905                limit_hash: limit.into(),
1906                response_bytes: 1_000,
1907            }),
1908            response,
1909        );
1910
1911        assert_eq!(
1912            rx.await.unwrap(),
1913            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1914                request_id: 3,
1915                slots: vec![Vec::new(), Vec::new()],
1916                proof: Vec::new(),
1917            }))
1918        );
1919        assert_eq!(
1920            provider.snap_storage_range_requests(),
1921            vec![
1922                (first_account, origin, limit, 1_000),
1923                (second_account, B256::ZERO, B256::repeat_byte(0xff), 1_000),
1924            ]
1925        );
1926        assert_eq!(provider.snap_state_range_resolutions(), 1);
1927    }
1928
1929    #[tokio::test]
1930    async fn snap_byte_codes_response_preserves_found_code_order() {
1931        let provider = MockEthProvider::default();
1932        let code = Bytes::from_static(&[0x60, 0x00]);
1933        let code_hash = keccak256(&code);
1934        let later_code = Bytes::from_static(&[0x60, 0x01]);
1935        let later_code_hash = keccak256(&later_code);
1936        provider.add_account(
1937            Address::repeat_byte(0x01),
1938            ExtendedAccount::new(1, U256::ZERO).with_bytecode(code.clone()),
1939        );
1940        provider.add_account(
1941            Address::repeat_byte(0x02),
1942            ExtendedAccount::new(1, U256::ZERO).with_bytecode(later_code),
1943        );
1944
1945        let mut handler = snap_handler(provider);
1946        let (response, rx) = oneshot::channel();
1947        handler.on_snap_request(
1948            PeerId::default(),
1949            SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1950                request_id: 3,
1951                hashes: vec![KECCAK_EMPTY, B256::repeat_byte(0xff), code_hash, later_code_hash],
1952                response_bytes: 1,
1953            }),
1954            response,
1955        );
1956
1957        assert_eq!(
1958            rx.await.unwrap(),
1959            Ok(SnapResponse::ByteCodes(ByteCodesMessage {
1960                request_id: 3,
1961                codes: vec![Bytes::new(), code],
1962            }))
1963        );
1964    }
1965
1966    #[tokio::test]
1967    async fn snap_storage_ranges_limit_account_lookups() {
1968        let provider = MockEthProvider::default();
1969        for _ in 0..=MAX_STORAGE_RANGE_ACCOUNTS_SERVE {
1970            provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1971        }
1972        let mut handler = snap_handler(provider.clone());
1973        let (response, rx) = oneshot::channel();
1974        handler.on_snap_request(
1975            PeerId::default(),
1976            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1977                request_id: 4,
1978                root_hash: B256::ZERO,
1979                account_hashes: vec![B256::ZERO; MAX_STORAGE_RANGE_ACCOUNTS_SERVE + 1],
1980                starting_hash: B256::ZERO.into(),
1981                limit_hash: B256::repeat_byte(0xff).into(),
1982                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1983            }),
1984            response,
1985        );
1986
1987        assert_eq!(
1988            rx.await.unwrap(),
1989            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1990                request_id: 4,
1991                slots: vec![Vec::new(); MAX_STORAGE_RANGE_ACCOUNTS_SERVE],
1992                proof: Vec::new(),
1993            }))
1994        );
1995        assert_eq!(provider.snap_storage_ranges_remaining(), 1);
1996        assert_eq!(provider.snap_state_range_resolutions(), 1);
1997    }
1998
1999    #[tokio::test]
2000    async fn snap_storage_range_proves_finite_limit_from_zero_origin() {
2001        let provider = MockEthProvider::default();
2002        let hash = B256::repeat_byte(0x01);
2003        let proof = vec![Bytes::from_static(&[0xcc])];
2004        // More entries exist beyond `limit_hash`, so the cursor stopped at the hash limit
2005        // rather than exhausting the trie -- a proof is required even though origin is zero.
2006        provider.push_snap_storage_range(vec![(hash, U256::ONE)], RangeEnd::HashLimit);
2007        provider.set_snap_storage_proof(Some(proof.clone()));
2008
2009        let mut handler = snap_handler(provider.clone());
2010        let (response, rx) = oneshot::channel();
2011        handler.on_snap_request(
2012            PeerId::default(),
2013            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
2014                request_id: 5,
2015                root_hash: B256::ZERO,
2016                account_hashes: vec![B256::repeat_byte(0x03)],
2017                starting_hash: B256::ZERO.into(),
2018                limit_hash: B256::repeat_byte(0x20).into(),
2019                response_bytes: SOFT_RESPONSE_LIMIT as u64,
2020            }),
2021            response,
2022        );
2023
2024        assert_eq!(
2025            rx.await.unwrap(),
2026            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
2027                request_id: 5,
2028                slots: vec![vec![StorageData { hash, data: Bytes::from_static(&[0x01]) }]],
2029                proof,
2030            }))
2031        );
2032    }
2033
2034    #[tokio::test]
2035    async fn snap_storage_ranges_are_entirely_empty_when_an_account_is_missing() {
2036        let provider = MockEthProvider::default();
2037        provider.push_missing_snap_storage_account();
2038        provider.push_snap_storage_range(
2039            vec![(B256::repeat_byte(0x01), U256::ONE)],
2040            RangeEnd::Exhausted,
2041        );
2042        let missing_account = B256::repeat_byte(0x01);
2043        let valid_account = B256::repeat_byte(0x02);
2044
2045        let mut handler = snap_handler(provider.clone());
2046        let (response, rx) = oneshot::channel();
2047        handler.on_snap_request(
2048            PeerId::default(),
2049            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
2050                request_id: 6,
2051                root_hash: B256::ZERO,
2052                account_hashes: vec![missing_account, valid_account],
2053                starting_hash: B256::ZERO.into(),
2054                limit_hash: B256::repeat_byte(0xff).into(),
2055                response_bytes: SOFT_RESPONSE_LIMIT as u64,
2056            }),
2057            response,
2058        );
2059
2060        assert_eq!(
2061            rx.await.unwrap(),
2062            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
2063                request_id: 6,
2064                slots: Vec::new(),
2065                proof: Vec::new(),
2066            }))
2067        );
2068        // The valid account's queued range is never consumed: the response bails out at the
2069        // first missing account instead of skipping it and shifting later positions.
2070        assert_eq!(provider.snap_storage_ranges_remaining(), 1);
2071    }
2072
2073    /// Creates a receipt whose single log carries `data_len` bytes of data.
2074    fn receipt_with_data(data_len: usize) -> Receipt {
2075        Receipt {
2076            tx_type: TxType::Legacy,
2077            success: true,
2078            cumulative_gas_used: 21_000,
2079            logs: vec![Log {
2080                address: Address::ZERO,
2081                data: LogData::new_unchecked(Vec::new(), vec![0u8; data_len].into()),
2082            }],
2083        }
2084    }
2085
2086    /// Stores the receipts as block `number` and returns the block hash.
2087    fn insert_receipts_block(
2088        provider: &MockEthProvider,
2089        number: u64,
2090        receipts: Vec<Receipt>,
2091    ) -> B256 {
2092        let hash = B256::with_last_byte(number as u8);
2093        provider.add_header(hash, Header { number, ..Default::default() });
2094        provider.add_receipts(number, receipts);
2095        hash
2096    }
2097
2098    /// Sends a [`GetReceipts70`] request to the handler and returns the response.
2099    async fn get_receipts70(
2100        provider: MockEthProvider,
2101        first_block_receipt_index: u64,
2102        block_hashes: Vec<B256>,
2103    ) -> Receipts70 {
2104        let handler = snap_handler(provider);
2105        let (response, rx) = oneshot::channel();
2106        handler.on_receipts70_request(
2107            PeerId::default(),
2108            GetReceipts70 { first_block_receipt_index, block_hashes },
2109            response,
2110        );
2111        rx.await.unwrap().unwrap()
2112    }
2113
2114    #[tokio::test]
2115    async fn receipts70_serves_block_over_soft_limit_complete() {
2116        let provider = MockEthProvider::default();
2117        // The block crosses the soft limit but fits within the message, so it is served whole.
2118        let small = receipt_with_data(16);
2119        let large = receipt_with_data(SOFT_RESPONSE_LIMIT);
2120        let hash = insert_receipts_block(&provider, 1, vec![small.clone(), large.clone()]);
2121
2122        let resp = get_receipts70(provider, 0, vec![hash]).await;
2123
2124        assert_eq!(
2125            resp,
2126            Receipts70 { last_block_incomplete: false, receipts: vec![vec![small, large]] }
2127        );
2128    }
2129
2130    #[tokio::test]
2131    async fn receipts70_serves_last_block_over_soft_limit_complete() {
2132        let provider = MockEthProvider::default();
2133        let first = vec![receipt_with_data(1024); 160];
2134        let first_hash = insert_receipts_block(&provider, 1, first.clone());
2135        // The second block crosses the soft limit but fits within the message, so it is served
2136        // whole after the first block.
2137        let second = vec![receipt_with_data(SOFT_RESPONSE_LIMIT)];
2138        let second_hash = insert_receipts_block(&provider, 2, second.clone());
2139
2140        let resp = get_receipts70(provider, 0, vec![first_hash, second_hash]).await;
2141
2142        assert_eq!(
2143            resp,
2144            Receipts70 { last_block_incomplete: false, receipts: vec![first, second] }
2145        );
2146    }
2147
2148    #[tokio::test]
2149    async fn receipts70_omits_block_whose_first_receipt_overflows_the_message() {
2150        let provider = MockEthProvider::default();
2151        let first = vec![receipt_with_data(SOFT_RESPONSE_LIMIT - 1024)];
2152        let first_hash = insert_receipts_block(&provider, 1, first.clone());
2153        // The first receipt of the second block fits in the message on its own but not in the
2154        // budget left after the first block, so the response ends after the first block.
2155        let second_hash =
2156            insert_receipts_block(&provider, 2, vec![receipt_with_data(9 * 1024 * 1024)]);
2157
2158        let resp = get_receipts70(provider, 0, vec![first_hash, second_hash]).await;
2159
2160        assert_eq!(resp, Receipts70 { last_block_incomplete: false, receipts: vec![first] });
2161    }
2162
2163    #[tokio::test]
2164    async fn receipts70_serves_partial_block_over_message_limit() {
2165        let provider = MockEthProvider::default();
2166        // Together the receipts overflow the 10 MiB message, so the block is split across
2167        // responses and flagged incomplete.
2168        let receipts = vec![receipt_with_data(1024 * 1024); 12];
2169        let hash = insert_receipts_block(&provider, 1, receipts.clone());
2170
2171        let resp = get_receipts70(provider, 0, vec![hash]).await;
2172
2173        assert!(resp.last_block_incomplete);
2174        assert_eq!(resp.receipts.len(), 1);
2175        let partial = &resp.receipts[0];
2176        assert!(!partial.is_empty() && partial.len() < receipts.len());
2177        assert_eq!(partial.as_slice(), &receipts[..partial.len()]);
2178
2179        let encoded = encode_receipts70_response(resp);
2180        // The split happens at the message limit, not the soft limit.
2181        assert!(encoded.len() > SOFT_RESPONSE_LIMIT);
2182        assert!(encoded.len() <= MAX_MESSAGE_SIZE);
2183    }
2184
2185    #[test_case(0, 0; "first receipt at hard limit")]
2186    #[test_case(1, 0; "continuation receipt at hard limit")]
2187    #[test_case(0, 1; "first receipt framing exceeds hard limit")]
2188    #[test_case(1, 1; "continuation receipt framing exceeds hard limit")]
2189    #[tokio::test]
2190    async fn receipts70_enforces_framed_response_limit(first_receipt_index: u64, excess: usize) {
2191        // Measure the actual wire encoding with the largest request ID. At these sizes, changing
2192        // the log data length does not change the width of any RLP length prefix.
2193        let data_len = MAX_MESSAGE_SIZE - 1024;
2194        let encoded = encode_receipts70_response(Receipts70 {
2195            last_block_incomplete: false,
2196            receipts: vec![vec![receipt_with_data(data_len)]],
2197        });
2198        let receipt = receipt_with_data(data_len + MAX_MESSAGE_SIZE - encoded.len() + excess);
2199        assert!(receipt.length() < MAX_MESSAGE_SIZE);
2200        let expected =
2201            Receipts70 { last_block_incomplete: false, receipts: vec![vec![receipt.clone()]] };
2202        assert_eq!(encode_receipts70_response(expected.clone()).len(), MAX_MESSAGE_SIZE + excess);
2203
2204        let provider = MockEthProvider::default();
2205        let mut receipts = vec![receipt_with_data(16); first_receipt_index as usize];
2206        receipts.push(receipt);
2207        let hash = insert_receipts_block(&provider, 1, receipts);
2208
2209        let resp = get_receipts70(provider, first_receipt_index, vec![hash]).await;
2210
2211        if excess == 0 {
2212            assert_eq!(resp, expected);
2213        } else {
2214            assert_eq!(resp, Receipts70 { last_block_incomplete: false, receipts: Vec::new() });
2215        }
2216        let encoded = encode_receipts70_response(resp);
2217        assert!(encoded.len() <= MAX_MESSAGE_SIZE);
2218        // The requesting side only decodes responses to requests it sent.
2219        let mut requester = EthStreamInner::<EthNetworkPrimitives>::new(EthVersion::Eth70);
2220        requester
2221            .encode_message(EthMessage::GetReceipts70(RequestPair {
2222                request_id: u64::MAX,
2223                message: GetReceipts70 {
2224                    first_block_receipt_index: first_receipt_index,
2225                    block_hashes: vec![hash],
2226                },
2227            }))
2228            .unwrap();
2229        requester.decode_message(encoded.as_ref().into()).unwrap();
2230    }
2231
2232    /// Encodes a receipts response with the largest possible request ID.
2233    fn encode_receipts70_response(response: Receipts70) -> Bytes {
2234        EthStreamInner::<EthNetworkPrimitives>::new(EthVersion::Eth70)
2235            .encode_message(EthMessage::Receipts70(RequestPair {
2236                request_id: u64::MAX,
2237                message: response,
2238            }))
2239            .unwrap()
2240            .into()
2241    }
2242}