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::Encodable;
11use futures::StreamExt;
12use reth_eth_wire::{
13    snap::{
14        AccountData, AccountRangeMessage, BlockAccessListsMessage, ByteCodesMessage,
15        GetAccountRangeMessage, GetStorageRangesMessage, SnapProtocolMessage, StorageData,
16        StorageRangesMessage,
17    },
18    BlockAccessLists, BlockBodies, BlockHeaders, Cells, EthNetworkPrimitives, GetBlockAccessLists,
19    GetBlockBodies, GetBlockHeaders, GetCells, GetNodeData, GetReceipts, GetReceipts70,
20    HeadersDirection, NetworkPrimitives, NodeData, Receipts, Receipts69, Receipts70,
21};
22use reth_network_api::test_utils::PeersHandle;
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    future::Future,
37    pin::Pin,
38    task::{Context, Poll},
39    time::Duration,
40};
41use tokio::sync::{mpsc::Receiver, oneshot};
42use tokio_stream::wrappers::ReceiverStream;
43
44// Limits: <https://github.com/ethereum/go-ethereum/blob/b0d44338bbcefee044f1f635a84487cbbd8f0538/eth/protocols/eth/handler.go#L34-L56>
45
46/// Maximum number of receipts to serve.
47///
48/// Used to limit lookups.
49pub const MAX_RECEIPTS_SERVE: usize = 1024;
50
51/// Maximum number of block headers to serve.
52///
53/// Used to limit lookups.
54pub const MAX_HEADERS_SERVE: usize = 1024;
55
56/// Maximum number of block headers to serve.
57///
58/// Used to limit lookups. With 24KB block sizes nowadays, the practical limit will always be
59/// `SOFT_RESPONSE_LIMIT`.
60pub const MAX_BODIES_SERVE: usize = 1024;
61
62/// Maximum number of block access lists to serve.
63///
64/// Used to limit lookups.
65pub const MAX_BLOCK_ACCESS_LISTS_SERVE: usize = 1024;
66
67/// Maximum number of cell lookups to serve.
68///
69/// Used to limit lookups.
70pub const MAX_CELLS_SERVE: usize = 1024;
71
72/// Maximum number of bytecode lookups to serve.
73///
74/// Used to limit lookups.
75pub const MAX_BYTE_CODES_SERVE: usize = 1024;
76
77/// Maximum number of storage range account lookups to serve.
78pub const MAX_STORAGE_RANGE_ACCOUNTS_SERVE: usize = 1024;
79
80/// Maximum size of replies to data retrievals: 2MB
81pub const SOFT_RESPONSE_LIMIT: usize = 2 * 1024 * 1024;
82
83/// Manages eth related requests on top of the p2p network.
84///
85/// This can be spawned to another task and is supposed to be run as background service.
86#[derive(Debug)]
87#[must_use = "Manager does nothing unless polled."]
88pub struct EthRequestHandler<C, N: NetworkPrimitives = EthNetworkPrimitives> {
89    /// The client type that can interact with the chain.
90    client: C,
91    /// Blob store used for serving blob cell requests.
92    blob_store: Box<dyn BlobStore>,
93    /// Used for reporting peers.
94    // TODO use to report spammers
95    #[expect(dead_code)]
96    peers: PeersHandle,
97    /// Incoming request from the [`NetworkManager`](crate::NetworkManager).
98    incoming_requests: ReceiverStream<IncomingEthRequest<N>>,
99    /// Metrics for the eth request handler.
100    metrics: EthRequestHandlerMetrics,
101}
102
103// === impl EthRequestHandler ===
104impl<C, N: NetworkPrimitives> EthRequestHandler<C, N> {
105    /// Create a new instance
106    pub fn new(client: C, peers: PeersHandle, incoming: Receiver<IncomingEthRequest<N>>) -> Self {
107        Self {
108            client,
109            blob_store: Box::<NoopBlobStore>::default(),
110            peers,
111            incoming_requests: ReceiverStream::new(incoming),
112            metrics: Default::default(),
113        }
114    }
115
116    /// Set blob store for the request handler
117    pub fn with_blob_store(mut self, blob_store: Box<dyn BlobStore>) -> Self {
118        self.blob_store = blob_store;
119        self
120    }
121}
122
123impl<C, N> EthRequestHandler<C, N>
124where
125    N: NetworkPrimitives,
126    C: BlockReader,
127{
128    /// Returns the list of requested headers
129    fn get_headers_response(&self, request: GetBlockHeaders) -> Vec<C::Header> {
130        let GetBlockHeaders { start_block, limit, skip, direction } = request;
131
132        let mut headers = Vec::new();
133
134        let mut block: BlockHashOrNumber = match start_block {
135            BlockHashOrNumber::Hash(start) => start.into(),
136            BlockHashOrNumber::Number(num) => {
137                let Some(hash) = self.client.block_hash(num).unwrap_or_default() else {
138                    return headers
139                };
140                hash.into()
141            }
142        };
143
144        let skip = skip as u64;
145        let mut total_bytes = 0;
146
147        for _ in 0..limit {
148            if let Some(header) = self.client.header_by_hash_or_number(block).unwrap_or_default() {
149                let number = header.number();
150                let parent_hash = header.parent_hash();
151
152                total_bytes += header.length();
153                headers.push(header);
154
155                if headers.len() >= MAX_HEADERS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
156                    break
157                }
158
159                match direction {
160                    HeadersDirection::Rising => {
161                        if let Some(next) = number.checked_add(1).and_then(|n| n.checked_add(skip))
162                        {
163                            block = next.into()
164                        } else {
165                            break
166                        }
167                    }
168                    HeadersDirection::Falling => {
169                        if skip > 0 {
170                            // prevent under flows for block.number == 0 and `block.number - skip <
171                            // 0`
172                            if let Some(next) =
173                                number.checked_sub(1).and_then(|num| num.checked_sub(skip))
174                            {
175                                block = next.into()
176                            } else {
177                                break
178                            }
179                        } else {
180                            block = parent_hash.into()
181                        }
182                    }
183                }
184            } else {
185                break
186            }
187        }
188
189        headers
190    }
191
192    fn on_headers_request(
193        &self,
194        _peer_id: PeerId,
195        request: GetBlockHeaders,
196        response: oneshot::Sender<RequestResult<BlockHeaders<C::Header>>>,
197    ) {
198        self.metrics.eth_headers_requests_received_total.increment(1);
199        let headers = self.get_headers_response(request);
200        let _ = response.send(Ok(BlockHeaders(headers)));
201    }
202
203    fn on_bodies_request(
204        &self,
205        _peer_id: PeerId,
206        request: GetBlockBodies,
207        response: oneshot::Sender<RequestResult<BlockBodies<<C::Block as Block>::Body>>>,
208    ) {
209        self.metrics.eth_bodies_requests_received_total.increment(1);
210        let mut bodies = Vec::new();
211
212        let mut total_bytes = 0;
213
214        for hash in request {
215            if let Some(block) = self.client.block_by_hash(hash).unwrap_or_default() {
216                let body = block.into_body();
217                total_bytes += body.length();
218                bodies.push(body);
219
220                if bodies.len() >= MAX_BODIES_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
221                    break
222                }
223            } else {
224                break
225            }
226        }
227
228        let _ = response.send(Ok(BlockBodies(bodies)));
229    }
230
231    /// Replies to `GetNodeData`.
232    ///
233    /// State serving via eth `GetNodeData` was removed; answer with an empty payload so eth/66
234    /// peers receive a response instead of hanging until the request timeout when the oneshot is
235    /// dropped unanswered.
236    fn on_node_data_request(
237        &self,
238        _peer_id: PeerId,
239        _request: GetNodeData,
240        response: oneshot::Sender<RequestResult<NodeData>>,
241    ) {
242        self.metrics.eth_node_data_requests_received_total.increment(1);
243        let _ = response.send(Ok(NodeData(vec![])));
244    }
245
246    fn on_receipts_request(
247        &self,
248        _peer_id: PeerId,
249        request: GetReceipts,
250        response: oneshot::Sender<RequestResult<Receipts<C::Receipt>>>,
251    ) {
252        self.metrics.eth_receipts_requests_received_total.increment(1);
253
254        let receipts = self.get_receipts_response(request, |receipts_by_block| {
255            receipts_by_block.into_iter().map(ReceiptWithBloom::from).collect::<Vec<_>>()
256        });
257
258        let _ = response.send(Ok(Receipts(receipts)));
259    }
260
261    fn on_receipts69_request(
262        &self,
263        _peer_id: PeerId,
264        request: GetReceipts,
265        response: oneshot::Sender<RequestResult<Receipts69<C::Receipt>>>,
266    ) {
267        self.metrics.eth_receipts_requests_received_total.increment(1);
268
269        let receipts = self.get_receipts_response(request, |receipts_by_block| {
270            // skip bloom filter for eth69
271            receipts_by_block
272        });
273
274        let _ = response.send(Ok(Receipts69(receipts)));
275    }
276
277    /// Handles partial responses for [`GetReceipts70`] queries.
278    ///
279    /// This will adhere to the soft limit but allow filling the last vec partially.
280    fn on_receipts70_request(
281        &self,
282        _peer_id: PeerId,
283        request: GetReceipts70,
284        response: oneshot::Sender<RequestResult<Receipts70<C::Receipt>>>,
285    ) {
286        self.metrics.eth_receipts_requests_received_total.increment(1);
287
288        let GetReceipts70 { first_block_receipt_index, block_hashes } = request;
289
290        let mut receipts = Vec::new();
291        let mut total_bytes = 0usize;
292        let mut last_block_incomplete = false;
293
294        for (idx, hash) in block_hashes.into_iter().enumerate() {
295            if idx >= MAX_RECEIPTS_SERVE {
296                break
297            }
298
299            let Some(mut block_receipts) =
300                self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
301            else {
302                break
303            };
304
305            if idx == 0 && first_block_receipt_index > 0 {
306                let skip = first_block_receipt_index as usize;
307                if skip >= block_receipts.len() {
308                    block_receipts.clear();
309                } else {
310                    block_receipts.drain(0..skip);
311                }
312            }
313
314            let block_size = block_receipts.length();
315
316            if total_bytes + block_size <= SOFT_RESPONSE_LIMIT {
317                total_bytes += block_size;
318                receipts.push(block_receipts);
319                continue;
320            }
321
322            let mut partial_block = Vec::new();
323            for receipt in block_receipts {
324                let receipt_size = receipt.length();
325                if total_bytes + receipt_size > SOFT_RESPONSE_LIMIT {
326                    break;
327                }
328                total_bytes += receipt_size;
329                partial_block.push(receipt);
330            }
331
332            receipts.push(partial_block);
333            last_block_incomplete = true;
334            break;
335        }
336
337        let _ = response.send(Ok(Receipts70 { last_block_incomplete, receipts }));
338    }
339
340    #[inline]
341    fn get_receipts_response<T, F>(&self, request: GetReceipts, transform_fn: F) -> Vec<Vec<T>>
342    where
343        F: Fn(Vec<C::Receipt>) -> Vec<T>,
344        T: Encodable,
345    {
346        let mut receipts = Vec::new();
347        let mut total_bytes = 0;
348
349        for hash in request {
350            if let Some(receipts_by_block) =
351                self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
352            {
353                let transformed_receipts = transform_fn(receipts_by_block);
354                total_bytes += transformed_receipts.length();
355                receipts.push(transformed_receipts);
356
357                if receipts.len() >= MAX_RECEIPTS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
358                    break
359                }
360            } else {
361                break
362            }
363        }
364
365        receipts
366    }
367
368    fn on_cells_request(
369        &self,
370        _peer_id: PeerId,
371        request: GetCells,
372        response: oneshot::Sender<RequestResult<Cells>>,
373    ) {
374        let mut cells_response = Cells { cell_mask: request.cell_mask, ..Default::default() };
375
376        for hash in request.hashes.into_iter().take(MAX_CELLS_SERVE) {
377            let Some(cells) =
378                self.blob_store.get_cells(hash, request.cell_mask).unwrap_or_default()
379            else {
380                continue;
381            };
382
383            cells_response.hashes.push(hash);
384            cells_response.cells.push(cells);
385
386            if cells_response.length() > SOFT_RESPONSE_LIMIT {
387                break
388            }
389        }
390
391        let _ = response.send(Ok(cells_response));
392    }
393}
394
395impl<C, N> EthRequestHandler<C, N>
396where
397    N: NetworkPrimitives,
398    C: BalProvider,
399{
400    /// Handles [`GetBlockAccessLists`] queries.
401    ///
402    /// EIP-8159 defines the final `BlockAccessLists` response semantics:
403    /// <https://eips.ethereum.org/EIPS/eip-8159>
404    fn on_block_access_lists_request(
405        &self,
406        _peer_id: PeerId,
407        mut request: GetBlockAccessLists,
408        response: oneshot::Sender<RequestResult<BlockAccessLists>>,
409    ) {
410        self.metrics.eth_block_access_lists_requests_received_total.increment(1);
411        request.0.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
412
413        let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(SOFT_RESPONSE_LIMIT);
414        let access_lists =
415            self.client.get_bals_by_hashes_with_limit(&request.0, limit).unwrap_or_default();
416        let _ = response.send(Ok(BlockAccessLists(access_lists)));
417    }
418}
419
420impl<C, N> EthRequestHandler<C, N>
421where
422    N: NetworkPrimitives,
423    C: BalProvider + StateProviderFactory + StateRangeProviderFactory,
424{
425    /// Handles `snap/2` (EIP-8189) requests.
426    ///
427    /// `GetAccountRange`/`GetStorageRanges` are hash-native throughout and served from retained
428    /// canonical roots via [`StateRangeProviderFactory`]. `GetByteCodes` is content-addressed and
429    /// independent of any particular state root, so it's served directly.
430    /// `GetBlockAccessLists` is answered from the same [`BalProvider`] store eth71's
431    /// `GetBlockAccessLists` uses, since both serve the same underlying data.
432    fn on_snap_request(
433        &self,
434        _peer_id: PeerId,
435        request: SnapProtocolMessage,
436        response: oneshot::Sender<RequestResult<SnapResponse>>,
437    ) {
438        self.metrics.snap_requests_received_total.increment(1);
439
440        let result = match request {
441            SnapProtocolMessage::GetAccountRange(req) => {
442                let request_id = req.request_id;
443                let response = self.get_account_range_response(req).unwrap_or_else(|error| {
444                    tracing::debug!(target: "net::snap", %error, "failed to serve account range");
445                    AccountRangeMessage { request_id, accounts: Vec::new(), proof: Vec::new() }
446                });
447                Ok(SnapResponse::AccountRange(response))
448            }
449            SnapProtocolMessage::GetStorageRanges(req) => {
450                let request_id = req.request_id;
451                let response = self.get_storage_ranges_response(req).unwrap_or_else(|error| {
452                    tracing::debug!(target: "net::snap", %error, "failed to serve storage ranges");
453                    StorageRangesMessage { request_id, slots: Vec::new(), proof: Vec::new() }
454                });
455                Ok(SnapResponse::StorageRanges(response))
456            }
457            SnapProtocolMessage::GetByteCodes(req) => {
458                let codes = self
459                    .get_byte_codes_response(&req.hashes, req.response_bytes as usize)
460                    .unwrap_or_else(|error| {
461                        tracing::debug!(target: "net::snap", %error, "failed to serve bytecodes");
462                        Vec::new()
463                    });
464                Ok(SnapResponse::ByteCodes(ByteCodesMessage { request_id: req.request_id, codes }))
465            }
466            SnapProtocolMessage::GetBlockAccessLists(mut req) => {
467                req.block_hashes.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
468                let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(
469                    (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT),
470                );
471                let block_access_lists = self
472                    .client
473                    .get_bals_by_hashes_with_limit(&req.block_hashes, limit)
474                    .unwrap_or_default();
475                Ok(SnapResponse::BlockAccessLists(BlockAccessListsMessage {
476                    request_id: req.request_id,
477                    block_access_lists: BlockAccessLists(block_access_lists),
478                }))
479            }
480            // The peer sent us a response-shaped message instead of a request; not something we
481            // asked for.
482            _ => Err(RequestError::BadResponse),
483        };
484
485        let _ = response.send(result);
486    }
487
488    /// Returns the bytecode for each of `hashes`, skipping hashes with no known code, stopping
489    /// once `response_bytes` (capped at [`SOFT_RESPONSE_LIMIT`]) is exceeded.
490    fn get_byte_codes_response(
491        &self,
492        hashes: &[B256],
493        response_bytes: usize,
494    ) -> ProviderResult<Vec<Bytes>> {
495        let state = self.client.latest()?;
496        let response_bytes = response_bytes.min(SOFT_RESPONSE_LIMIT);
497
498        let mut codes = Vec::new();
499        let mut total_bytes = 0;
500        for hash in hashes.iter().take(MAX_BYTE_CODES_SERVE) {
501            let bytes = if *hash == KECCAK_EMPTY {
502                Bytes::new()
503            } else {
504                match state.bytecode_by_hash(hash)? {
505                    Some(bytecode) => bytecode.original_bytes(),
506                    None => continue,
507                }
508            };
509            total_bytes += bytes.len();
510            codes.push(bytes);
511
512            if total_bytes > response_bytes {
513                break
514            }
515        }
516        Ok(codes)
517    }
518
519    /// Serves a `GetAccountRange` request via [`StateRangeProviderFactory`].
520    ///
521    /// Fails the request if a storage root or proof becomes unavailable after the range lookup.
522    /// Proves the boundary between `starting_hash` and the last returned account, per snap/2's
523    /// boundary-proof requirement, unless the range is a zero-origin range that exhausted the
524    /// trie, in which case no proof is needed.
525    fn get_account_range_response(
526        &self,
527        req: GetAccountRangeMessage,
528    ) -> ProviderResult<AccountRangeMessage> {
529        let empty = AccountRangeMessage {
530            request_id: req.request_id,
531            accounts: Vec::new(),
532            proof: Vec::new(),
533        };
534
535        let response_bytes = (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT);
536        let Some(state) = self.client.state_range_provider(req.root_hash)? else {
537            return Ok(empty)
538        };
539        let RangeResponse { items: accounts, end } =
540            state.account_range(req.starting_hash, req.limit_hash, response_bytes)?;
541
542        let proof = if req.starting_hash == B256::ZERO && end == RangeEnd::Exhausted {
543            Vec::new()
544        } else {
545            let boundary_keys = boundary_proof_keys(req.starting_hash, accounts.last());
546            state.account_range_proof(&boundary_keys)?
547        };
548
549        let mut account_data = Vec::with_capacity(accounts.len());
550        for (hash, account) in accounts {
551            let storage_root = state.storage_root_by_hash(hash)?;
552            account_data.push(AccountData::from_trie_account(
553                hash,
554                &account.into_trie_account(storage_root),
555            ));
556        }
557
558        Ok(AccountRangeMessage { request_id: req.request_id, accounts: account_data, proof })
559    }
560
561    /// Serves a `GetStorageRanges` request via [`StateRangeProviderFactory`].
562    ///
563    /// `starting_hash`/`limit_hash` apply only to the first account. An account unavailable at
564    /// this root makes the whole response empty, rather than skipping it and shifting later
565    /// accounts' positions. A proof stops the response at the first account that isn't a
566    /// complete, zero-origin range.
567    fn get_storage_ranges_response(
568        &self,
569        req: GetStorageRangesMessage,
570    ) -> ProviderResult<StorageRangesMessage> {
571        let empty = StorageRangesMessage {
572            request_id: req.request_id,
573            slots: Vec::new(),
574            proof: Vec::new(),
575        };
576        let Some(state) = self.client.state_range_provider(req.root_hash)? else {
577            return Ok(empty)
578        };
579        let mut slots: Vec<Vec<StorageData>> = Vec::new();
580        let mut proof = Vec::new();
581        let mut remaining_bytes = (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT);
582
583        for (i, &hashed_address) in
584            req.account_hashes.iter().take(MAX_STORAGE_RANGE_ACCOUNTS_SERVE).enumerate()
585        {
586            // Keeps traversing storage-empty accounts under a zero budget so a real slot still
587            // gets served; worst case is a lookup per account, bounded by the take() above.
588            if remaining_bytes == 0 && slots.iter().any(|range| !range.is_empty()) {
589                break
590            }
591            let (origin, limit) = if i == 0 {
592                (
593                    req.starting_hash.unwrap_or(B256::ZERO),
594                    req.limit_hash.unwrap_or(B256::repeat_byte(0xff)),
595                )
596            } else {
597                (B256::ZERO, B256::repeat_byte(0xff))
598            };
599            let Some(RangeResponse { items: account_slots, end }) =
600                state.storage_range(hashed_address, origin, limit, remaining_bytes)?
601            else {
602                return Ok(empty)
603            };
604
605            remaining_bytes = remaining_bytes.saturating_sub(account_slots.len() * 64);
606            let last = account_slots.last().map(|(hash, _)| *hash);
607            let needs_proof = origin != B256::ZERO || end != RangeEnd::Exhausted;
608            slots.push(
609                account_slots
610                    .into_iter()
611                    // snap clients verify proofs against RLP-encoded storage trie leaves.
612                    .map(|(hash, value)| StorageData::from_value(hash, value))
613                    .collect(),
614            );
615
616            if needs_proof {
617                let boundary_keys = match last {
618                    Some(last) => vec![origin, last],
619                    None => vec![origin],
620                };
621                proof = state.storage_range_proof(hashed_address, &boundary_keys)?;
622                break
623            }
624        }
625
626        Ok(StorageRangesMessage { request_id: req.request_id, slots, proof })
627    }
628}
629
630/// Boundary-proof keys for a range reply: `origin`, plus the last returned item's key if any.
631fn boundary_proof_keys<T>(origin: B256, last: Option<&(B256, T)>) -> Vec<B256> {
632    match last {
633        Some((last, _)) => vec![origin, *last],
634        None => vec![origin],
635    }
636}
637
638/// An endless future.
639///
640/// This should be spawned or used as part of `tokio::select!`.
641impl<C, N> Future for EthRequestHandler<C, N>
642where
643    N: NetworkPrimitives,
644    C: BalProvider
645        + StateProviderFactory
646        + StateRangeProviderFactory
647        + BlockReader<Block = N::Block, Receipt = N::Receipt>
648        + HeaderProvider<Header = N::BlockHeader>
649        + Unpin,
650{
651    type Output = ();
652
653    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
654        let this = self.get_mut();
655
656        let mut acc = Duration::ZERO;
657        let maybe_more_incoming_requests = metered_poll_nested_stream_with_budget!(
658            acc,
659            "net::eth",
660            "Incoming eth requests stream",
661            DEFAULT_BUDGET_TRY_DRAIN_DOWNLOADERS,
662            this.incoming_requests.poll_next_unpin(cx),
663            |incoming| {
664                match incoming {
665                    IncomingEthRequest::GetBlockHeaders { peer_id, request, response } => {
666                        this.on_headers_request(peer_id, request, response)
667                    }
668                    IncomingEthRequest::GetBlockBodies { peer_id, request, response } => {
669                        this.on_bodies_request(peer_id, request, response)
670                    }
671                    IncomingEthRequest::GetNodeData { peer_id, request, response } => {
672                        this.on_node_data_request(peer_id, request, response)
673                    }
674                    IncomingEthRequest::GetReceipts { peer_id, request, response } => {
675                        this.on_receipts_request(peer_id, request, response)
676                    }
677                    IncomingEthRequest::GetReceipts69 { peer_id, request, response } => {
678                        this.on_receipts69_request(peer_id, request, response)
679                    }
680                    IncomingEthRequest::GetReceipts70 { peer_id, request, response } => {
681                        this.on_receipts70_request(peer_id, request, response)
682                    }
683                    IncomingEthRequest::GetBlockAccessLists { peer_id, request, response } => {
684                        this.on_block_access_lists_request(peer_id, request, response)
685                    }
686                    IncomingEthRequest::GetCells { peer_id, request, response } => {
687                        this.on_cells_request(peer_id, request, response)
688                    }
689                    IncomingEthRequest::GetSnap { peer_id, request, response } => {
690                        this.on_snap_request(peer_id, request, response)
691                    }
692                }
693            },
694        );
695
696        this.metrics.acc_duration_poll_eth_req_handler.set(acc.as_secs_f64());
697
698        // stream is fully drained and import futures pending
699        if maybe_more_incoming_requests {
700            // make sure we're woken up again
701            cx.waker().wake_by_ref();
702        }
703
704        Poll::Pending
705    }
706}
707
708/// All `eth` request related to blocks delegated by the network.
709#[derive(Debug)]
710pub enum IncomingEthRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
711    /// Request Block headers from the peer.
712    ///
713    /// The response should be sent through the channel.
714    GetBlockHeaders {
715        /// The ID of the peer to request block headers from.
716        peer_id: PeerId,
717        /// The specific block headers requested.
718        request: GetBlockHeaders,
719        /// The channel sender for the response containing block headers.
720        response: oneshot::Sender<RequestResult<BlockHeaders<N::BlockHeader>>>,
721    },
722    /// Request Block bodies from the peer.
723    ///
724    /// The response should be sent through the channel.
725    GetBlockBodies {
726        /// The ID of the peer to request block bodies from.
727        peer_id: PeerId,
728        /// The specific block bodies requested.
729        request: GetBlockBodies,
730        /// The channel sender for the response containing block bodies.
731        response: oneshot::Sender<RequestResult<BlockBodies<N::BlockBody>>>,
732    },
733    /// Request Node Data from the peer.
734    ///
735    /// The response should be sent through the channel.
736    GetNodeData {
737        /// The ID of the peer to request node data from.
738        peer_id: PeerId,
739        /// The specific node data requested.
740        request: GetNodeData,
741        /// The channel sender for the response containing node data.
742        response: oneshot::Sender<RequestResult<NodeData>>,
743    },
744    /// Request Receipts from the peer.
745    ///
746    /// The response should be sent through the channel.
747    GetReceipts {
748        /// The ID of the peer to request receipts from.
749        peer_id: PeerId,
750        /// The specific receipts requested.
751        request: GetReceipts,
752        /// The channel sender for the response containing receipts.
753        response: oneshot::Sender<RequestResult<Receipts<N::Receipt>>>,
754    },
755    /// Request Receipts from the peer without bloom filter.
756    ///
757    /// The response should be sent through the channel.
758    GetReceipts69 {
759        /// The ID of the peer to request receipts from.
760        peer_id: PeerId,
761        /// The specific receipts requested.
762        request: GetReceipts,
763        /// The channel sender for the response containing Receipts69.
764        response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
765    },
766    /// Request Receipts from the peer using eth/70.
767    ///
768    /// The response should be sent through the channel.
769    GetReceipts70 {
770        /// The ID of the peer to request receipts from.
771        peer_id: PeerId,
772        /// The specific receipts requested including the `firstBlockReceiptIndex`.
773        request: GetReceipts70,
774        /// The channel sender for the response containing Receipts70.
775        response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
776    },
777    /// Request Block Access Lists from the peer.
778    ///
779    /// The response should be sent through the channel.
780    GetBlockAccessLists {
781        /// The ID of the peer to request block access lists from.
782        peer_id: PeerId,
783        /// The requested block hashes.
784        request: GetBlockAccessLists,
785        /// The channel sender for the response containing block access lists.
786        response: oneshot::Sender<RequestResult<BlockAccessLists>>,
787    },
788    /// Request Cells from the peer.
789    ///
790    /// The response should be sent through the channel.
791    GetCells {
792        /// The ID of the peer to request cells from.
793        peer_id: PeerId,
794        /// The requested block hashes.
795        request: GetCells,
796        /// The channel sender for the response containing cells.
797        response: oneshot::Sender<RequestResult<Cells>>,
798    },
799    /// Request a `snap/2` message from the peer.
800    ///
801    /// The response should be sent through the channel.
802    GetSnap {
803        /// The ID of the peer to request from.
804        peer_id: PeerId,
805        /// The `snap/2` request.
806        request: SnapProtocolMessage,
807        /// The channel sender for the response.
808        response: oneshot::Sender<RequestResult<SnapResponse>>,
809    },
810}
811
812#[cfg(test)]
813mod tests {
814    use super::*;
815    use alloy_consensus::constants::EMPTY_ROOT_HASH;
816    use alloy_eips::{
817        eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
818        eip7594::{BlobTransactionSidecarVariant, Cell},
819    };
820    use alloy_primitives::{keccak256, Address, TxHash, B128, U256};
821    use reth_network_api::test_utils::PeersHandle;
822    use reth_primitives_traits::Account;
823    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
824    use reth_storage_api::noop::NoopProvider;
825    use reth_transaction_pool::blobstore::{
826        BlobStoreCleanupStat, BlobStoreError, PooledBlobSidecar,
827    };
828    use std::sync::{
829        atomic::{AtomicUsize, Ordering},
830        Arc,
831    };
832    use test_case::test_case;
833    use tokio::sync::mpsc;
834
835    #[derive(Debug, Default)]
836    struct CountingBlobStore {
837        get_cells_calls: Arc<AtomicUsize>,
838    }
839
840    impl BlobStore for CountingBlobStore {
841        fn insert(&self, _tx: B256, _data: PooledBlobSidecar) -> Result<(), BlobStoreError> {
842            Ok(())
843        }
844
845        fn insert_all(&self, _txs: Vec<(B256, PooledBlobSidecar)>) -> Result<(), BlobStoreError> {
846            Ok(())
847        }
848
849        fn delete(&self, _tx: B256) -> Result<(), BlobStoreError> {
850            Ok(())
851        }
852
853        fn delete_all(&self, _txs: Vec<B256>) -> Result<(), BlobStoreError> {
854            Ok(())
855        }
856
857        fn cleanup(&self) -> BlobStoreCleanupStat {
858            BlobStoreCleanupStat::default()
859        }
860
861        fn get(
862            &self,
863            _tx: B256,
864        ) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError> {
865            Ok(None)
866        }
867
868        fn contains(&self, _tx: B256) -> Result<bool, BlobStoreError> {
869            Ok(false)
870        }
871
872        fn get_all(
873            &self,
874            _txs: Vec<B256>,
875        ) -> Result<Vec<(B256, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError> {
876            Ok(vec![])
877        }
878
879        fn get_exact(
880            &self,
881            txs: Vec<B256>,
882        ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError> {
883            if txs.is_empty() {
884                return Ok(vec![])
885            }
886
887            Err(BlobStoreError::MissingSidecar(txs[0]))
888        }
889
890        fn get_by_versioned_hashes_v1(
891            &self,
892            versioned_hashes: &[B256],
893        ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError> {
894            Ok(vec![None; versioned_hashes.len()])
895        }
896
897        fn get_by_versioned_hashes_v2(
898            &self,
899            _versioned_hashes: &[B256],
900        ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError> {
901            Ok(None)
902        }
903
904        fn get_by_versioned_hashes_v3(
905            &self,
906            versioned_hashes: &[B256],
907        ) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError> {
908            Ok(vec![None; versioned_hashes.len()])
909        }
910
911        fn get_by_versioned_hashes_v4(
912            &self,
913            versioned_hashes: &[B256],
914            _indices_bitarray: B128,
915        ) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
916            Ok(vec![None; versioned_hashes.len()])
917        }
918
919        fn has_versioned_hashes(
920            &self,
921            versioned_hashes: &[B256],
922        ) -> Result<Vec<bool>, BlobStoreError> {
923            Ok(vec![false; versioned_hashes.len()])
924        }
925
926        fn get_cells(
927            &self,
928            _tx_hash: TxHash,
929            _indices_bitarray: B128,
930        ) -> Result<Option<Vec<Cell>>, BlobStoreError> {
931            self.get_cells_calls.fetch_add(1, Ordering::Relaxed);
932            Ok(None)
933        }
934
935        fn data_size_hint(&self) -> Option<usize> {
936            Some(0)
937        }
938
939        fn blobs_len(&self) -> usize {
940            0
941        }
942    }
943
944    #[tokio::test]
945    async fn get_cells_request_limits_blob_store_lookups() {
946        let (peers_tx, _) = mpsc::unbounded_channel();
947        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
948        let get_cells_calls = Arc::new(AtomicUsize::new(0));
949        let blob_store = CountingBlobStore { get_cells_calls: Arc::clone(&get_cells_calls) };
950        let handler = EthRequestHandler::<NoopProvider>::new(
951            NoopProvider::default(),
952            PeersHandle::new(peers_tx),
953            incoming_rx,
954        )
955        .with_blob_store(Box::new(blob_store));
956        let (response, rx) = oneshot::channel();
957        let request =
958            GetCells { hashes: vec![B256::ZERO; MAX_CELLS_SERVE + 1], cell_mask: B128::default() };
959
960        handler.on_cells_request(PeerId::default(), request, response);
961
962        let cells = rx.await.unwrap().unwrap();
963        assert!(cells.hashes.is_empty());
964        assert_eq!(get_cells_calls.load(Ordering::Relaxed), MAX_CELLS_SERVE);
965    }
966
967    #[tokio::test]
968    async fn get_node_data_responds_with_empty_payload() {
969        let (peers_tx, _) = mpsc::unbounded_channel();
970        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
971        let handler = EthRequestHandler::<NoopProvider>::new(
972            NoopProvider::default(),
973            PeersHandle::new(peers_tx),
974            incoming_rx,
975        );
976        let (response, rx) = oneshot::channel();
977
978        handler.on_node_data_request(PeerId::default(), GetNodeData(vec![B256::ZERO]), response);
979
980        let node_data = rx.await.expect("response channel must not be dropped").unwrap();
981        assert!(node_data.0.is_empty(), "GetNodeData should reply with empty NodeData");
982    }
983
984    /// Creates a request handler backed by the mock provider for snap response tests.
985    fn snap_handler(
986        provider: MockEthProvider,
987    ) -> EthRequestHandler<MockEthProvider, EthNetworkPrimitives> {
988        let (peers_tx, _) = mpsc::unbounded_channel();
989        let (_incoming_tx, incoming_rx) = mpsc::channel(1);
990        EthRequestHandler::new(provider, PeersHandle::new(peers_tx), incoming_rx)
991    }
992
993    #[test_case(
994        SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
995            request_id: 1,
996            root_hash: B256::ZERO,
997            starting_hash: B256::ZERO,
998            limit_hash: B256::repeat_byte(0xff),
999            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1000        }),
1001        SnapResponse::AccountRange(AccountRangeMessage {
1002            request_id: 1,
1003            accounts: Vec::new(),
1004            proof: Vec::new(),
1005        }); "account range"
1006    )]
1007    #[test_case(
1008        SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1009            request_id: 2,
1010            root_hash: B256::ZERO,
1011            account_hashes: vec![B256::ZERO],
1012            starting_hash: B256::ZERO.into(),
1013            limit_hash: B256::repeat_byte(0xff).into(),
1014            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1015        }),
1016        SnapResponse::StorageRanges(StorageRangesMessage {
1017            request_id: 2,
1018            slots: Vec::new(),
1019            proof: Vec::new(),
1020        }); "storage ranges"
1021    )]
1022    #[test_case(
1023        SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1024            request_id: 3,
1025            hashes: vec![B256::repeat_byte(0x11)],
1026            response_bytes: SOFT_RESPONSE_LIMIT as u64,
1027        }),
1028        SnapResponse::ByteCodes(ByteCodesMessage { request_id: 3, codes: Vec::new() }); "bytecodes"
1029    )]
1030    #[tokio::test]
1031    async fn snap_requests_return_empty_responses_on_provider_errors(
1032        request: SnapProtocolMessage,
1033        expected: SnapResponse,
1034    ) {
1035        let provider = MockEthProvider::default();
1036        provider.set_snap_state_reads_fail(true);
1037        let handler = snap_handler(provider);
1038        let (response, rx) = oneshot::channel();
1039
1040        handler.on_snap_request(PeerId::default(), request, response);
1041
1042        assert_eq!(rx.await.unwrap(), Ok(expected));
1043    }
1044
1045    #[tokio::test]
1046    async fn unavailable_snap_state_returns_empty_response() {
1047        let provider = MockEthProvider::default();
1048        let handler = snap_handler(provider.clone());
1049        let (response, rx) = oneshot::channel();
1050        handler.on_snap_request(
1051            PeerId::default(),
1052            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1053                request_id: 1,
1054                root_hash: B256::ZERO,
1055                starting_hash: B256::ZERO,
1056                limit_hash: B256::repeat_byte(0xff),
1057                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1058            }),
1059            response,
1060        );
1061
1062        assert_eq!(
1063            rx.await.unwrap(),
1064            Ok(SnapResponse::AccountRange(AccountRangeMessage {
1065                request_id: 1,
1066                accounts: Vec::new(),
1067                proof: Vec::new(),
1068            }))
1069        );
1070        assert_eq!(provider.snap_state_range_resolutions(), 1);
1071    }
1072
1073    #[tokio::test]
1074    async fn snap_requests_return_empty_responses_on_inconsistent_provider_results() {
1075        let missing_storage_root = MockEthProvider::default();
1076        missing_storage_root
1077            .set_snap_account_range(vec![(B256::ZERO, Account::default())], RangeEnd::Exhausted);
1078
1079        let missing_account_proof = MockEthProvider::default();
1080        missing_account_proof.set_snap_account_range(Vec::new(), RangeEnd::Exhausted);
1081
1082        let missing_storage_proof = MockEthProvider::default();
1083        missing_storage_proof
1084            .push_snap_storage_range(vec![(B256::ZERO, U256::from(1))], RangeEnd::ByteLimit);
1085
1086        let storage_disappears = MockEthProvider::default();
1087        storage_disappears.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1088        storage_disappears.push_unavailable_snap_storage_range();
1089
1090        let account_request = || {
1091            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1092                request_id: 1,
1093                root_hash: B256::ZERO,
1094                starting_hash: B256::ZERO,
1095                limit_hash: B256::repeat_byte(0xff),
1096                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1097            })
1098        };
1099        let storage_request = |account_hashes| {
1100            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1101                request_id: 2,
1102                root_hash: B256::ZERO,
1103                account_hashes,
1104                starting_hash: B256::ZERO.into(),
1105                limit_hash: B256::repeat_byte(0xff).into(),
1106                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1107            })
1108        };
1109        let empty_accounts = SnapResponse::AccountRange(AccountRangeMessage {
1110            request_id: 1,
1111            accounts: Vec::new(),
1112            proof: Vec::new(),
1113        });
1114        let empty_storage = SnapResponse::StorageRanges(StorageRangesMessage {
1115            request_id: 2,
1116            slots: Vec::new(),
1117            proof: Vec::new(),
1118        });
1119        let cases = [
1120            (missing_storage_root, account_request(), empty_accounts.clone()),
1121            (missing_account_proof, account_request(), empty_accounts),
1122            (missing_storage_proof, storage_request(vec![B256::ZERO]), empty_storage.clone()),
1123            (storage_disappears, storage_request(vec![B256::ZERO, B256::ZERO]), empty_storage),
1124        ];
1125
1126        for (provider, request, expected) in cases {
1127            let handler = snap_handler(provider.clone());
1128            let (response, rx) = oneshot::channel();
1129            handler.on_snap_request(PeerId::default(), request, response);
1130            assert_eq!(rx.await.unwrap(), Ok(expected));
1131            assert_eq!(provider.snap_state_range_resolutions(), 1);
1132        }
1133    }
1134
1135    #[tokio::test]
1136    async fn snap_account_range_response_encodes_accounts_and_proof() {
1137        let provider = MockEthProvider::default();
1138        let first_hash = B256::repeat_byte(0x01);
1139        let second_hash = B256::repeat_byte(0x02);
1140        let storage_root = B256::repeat_byte(0x11);
1141        let code_hash = B256::repeat_byte(0x22);
1142        let proof = vec![Bytes::from_static(&[0xaa])];
1143        provider.set_snap_account_range(
1144            vec![
1145                (
1146                    first_hash,
1147                    Account { nonce: 1, balance: U256::from(2), bytecode_hash: Some(code_hash) },
1148                ),
1149                (second_hash, Account { nonce: 3, balance: U256::from(4), bytecode_hash: None }),
1150            ],
1151            // A hash-limit stop (not an exhausted trie) so a boundary proof is still expected,
1152            // matching the mocked `proof` below.
1153            RangeEnd::HashLimit,
1154        );
1155        provider.set_snap_storage_root(first_hash, storage_root);
1156        provider.set_snap_storage_root(second_hash, EMPTY_ROOT_HASH);
1157        provider.set_snap_account_proof(Some(proof.clone()));
1158
1159        let mut full_body = vec![0xf8, 0x44, 0x01, 0x02, 0xa0];
1160        full_body.extend_from_slice(storage_root.as_slice());
1161        full_body.push(0xa0);
1162        full_body.extend_from_slice(code_hash.as_slice());
1163        let empty_body = Bytes::from_static(&[0xc4, 0x03, 0x04, 0x80, 0x80]);
1164
1165        let handler = snap_handler(provider.clone());
1166        let (response, rx) = oneshot::channel();
1167        handler.on_snap_request(
1168            PeerId::default(),
1169            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1170                request_id: 1,
1171                root_hash: B256::ZERO,
1172                starting_hash: B256::ZERO,
1173                limit_hash: B256::repeat_byte(0xff),
1174                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1175            }),
1176            response,
1177        );
1178
1179        assert_eq!(
1180            rx.await.unwrap(),
1181            Ok(SnapResponse::AccountRange(AccountRangeMessage {
1182                request_id: 1,
1183                accounts: vec![
1184                    AccountData { hash: first_hash, body: full_body.into() },
1185                    AccountData { hash: second_hash, body: empty_body },
1186                ],
1187                proof,
1188            }))
1189        );
1190        assert_eq!(provider.snap_state_range_resolutions(), 1);
1191    }
1192
1193    #[tokio::test]
1194    async fn snap_account_range_response_skips_proof_for_exhausted_zero_origin_range() {
1195        let provider = MockEthProvider::default();
1196        let hash = B256::repeat_byte(0x01);
1197        provider.set_snap_account_range(
1198            vec![(hash, Account { nonce: 1, balance: U256::from(2), bytecode_hash: None })],
1199            RangeEnd::Exhausted,
1200        );
1201        provider.set_snap_storage_root(hash, EMPTY_ROOT_HASH);
1202        // A non-empty mocked proof: if the skip-proof optimization regressed to "never skip",
1203        // this wrongly-included proof would surface in the response and fail the assertion below.
1204        provider.set_snap_account_proof(Some(vec![Bytes::from_static(&[0xaa])]));
1205
1206        let handler = snap_handler(provider.clone());
1207        let (response, rx) = oneshot::channel();
1208        handler.on_snap_request(
1209            PeerId::default(),
1210            SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1211                request_id: 1,
1212                root_hash: B256::ZERO,
1213                starting_hash: B256::ZERO,
1214                limit_hash: B256::repeat_byte(0xff),
1215                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1216            }),
1217            response,
1218        );
1219
1220        let Ok(SnapResponse::AccountRange(AccountRangeMessage { accounts, proof, .. })) =
1221            rx.await.unwrap()
1222        else {
1223            panic!("expected an account range response");
1224        };
1225        assert_eq!(accounts.len(), 1);
1226        assert!(proof.is_empty(), "a zero-origin, exhausted range must not carry a boundary proof");
1227    }
1228
1229    #[tokio::test]
1230    async fn snap_storage_range_response_encodes_values_and_proof() {
1231        let provider = MockEthProvider::default();
1232        let first_hash = B256::repeat_byte(0x01);
1233        let second_hash = B256::repeat_byte(0x02);
1234        let origin = B256::repeat_byte(0x10);
1235        let proof = vec![Bytes::from_static(&[0xbb])];
1236        provider.push_snap_storage_range(
1237            vec![(first_hash, U256::from(0x0102)), (second_hash, U256::from(0xff))],
1238            RangeEnd::Exhausted,
1239        );
1240        provider.set_snap_storage_proof(Some(proof.clone()));
1241
1242        let handler = snap_handler(provider.clone());
1243        let (response, rx) = oneshot::channel();
1244        handler.on_snap_request(
1245            PeerId::default(),
1246            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1247                request_id: 2,
1248                root_hash: B256::ZERO,
1249                account_hashes: vec![B256::repeat_byte(0x03)],
1250                starting_hash: origin.into(),
1251                limit_hash: B256::repeat_byte(0xff).into(),
1252                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1253            }),
1254            response,
1255        );
1256
1257        assert_eq!(
1258            rx.await.unwrap(),
1259            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1260                request_id: 2,
1261                slots: vec![vec![
1262                    StorageData { hash: first_hash, data: Bytes::from_static(&[0x82, 0x01, 0x02]) },
1263                    StorageData { hash: second_hash, data: Bytes::from_static(&[0x81, 0xff]) },
1264                ]],
1265                proof,
1266            }))
1267        );
1268        assert_eq!(provider.snap_storage_range_requests()[0].1, origin);
1269        assert_eq!(provider.snap_state_range_resolutions(), 1);
1270    }
1271
1272    #[tokio::test]
1273    async fn snap_storage_ranges_only_bound_the_first_account() {
1274        let provider = MockEthProvider::default();
1275        provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1276        provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1277        let first_account = B256::repeat_byte(0x01);
1278        let second_account = B256::repeat_byte(0x02);
1279        let origin = B256::ZERO;
1280        let limit = B256::repeat_byte(0x22);
1281
1282        let handler = snap_handler(provider.clone());
1283        let (response, rx) = oneshot::channel();
1284        handler.on_snap_request(
1285            PeerId::default(),
1286            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1287                request_id: 3,
1288                root_hash: B256::ZERO,
1289                account_hashes: vec![first_account, second_account],
1290                starting_hash: origin.into(),
1291                limit_hash: limit.into(),
1292                response_bytes: 1_000,
1293            }),
1294            response,
1295        );
1296
1297        assert_eq!(
1298            rx.await.unwrap(),
1299            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1300                request_id: 3,
1301                slots: vec![Vec::new(), Vec::new()],
1302                proof: Vec::new(),
1303            }))
1304        );
1305        assert_eq!(
1306            provider.snap_storage_range_requests(),
1307            vec![
1308                (first_account, origin, limit, 1_000),
1309                (second_account, B256::ZERO, B256::repeat_byte(0xff), 1_000),
1310            ]
1311        );
1312        assert_eq!(provider.snap_state_range_resolutions(), 1);
1313    }
1314
1315    #[tokio::test]
1316    async fn snap_byte_codes_response_preserves_found_code_order() {
1317        let provider = MockEthProvider::default();
1318        let code = Bytes::from_static(&[0x60, 0x00]);
1319        let code_hash = keccak256(&code);
1320        let later_code = Bytes::from_static(&[0x60, 0x01]);
1321        let later_code_hash = keccak256(&later_code);
1322        provider.add_account(
1323            Address::repeat_byte(0x01),
1324            ExtendedAccount::new(1, U256::ZERO).with_bytecode(code.clone()),
1325        );
1326        provider.add_account(
1327            Address::repeat_byte(0x02),
1328            ExtendedAccount::new(1, U256::ZERO).with_bytecode(later_code),
1329        );
1330
1331        let handler = snap_handler(provider);
1332        let (response, rx) = oneshot::channel();
1333        handler.on_snap_request(
1334            PeerId::default(),
1335            SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1336                request_id: 3,
1337                hashes: vec![KECCAK_EMPTY, B256::repeat_byte(0xff), code_hash, later_code_hash],
1338                response_bytes: 1,
1339            }),
1340            response,
1341        );
1342
1343        assert_eq!(
1344            rx.await.unwrap(),
1345            Ok(SnapResponse::ByteCodes(ByteCodesMessage {
1346                request_id: 3,
1347                codes: vec![Bytes::new(), code],
1348            }))
1349        );
1350    }
1351
1352    #[tokio::test]
1353    async fn snap_storage_ranges_limit_account_lookups() {
1354        let provider = MockEthProvider::default();
1355        for _ in 0..=MAX_STORAGE_RANGE_ACCOUNTS_SERVE {
1356            provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1357        }
1358        let handler = snap_handler(provider.clone());
1359        let (response, rx) = oneshot::channel();
1360        handler.on_snap_request(
1361            PeerId::default(),
1362            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1363                request_id: 4,
1364                root_hash: B256::ZERO,
1365                account_hashes: vec![B256::ZERO; MAX_STORAGE_RANGE_ACCOUNTS_SERVE + 1],
1366                starting_hash: B256::ZERO.into(),
1367                limit_hash: B256::repeat_byte(0xff).into(),
1368                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1369            }),
1370            response,
1371        );
1372
1373        assert_eq!(
1374            rx.await.unwrap(),
1375            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1376                request_id: 4,
1377                slots: vec![Vec::new(); MAX_STORAGE_RANGE_ACCOUNTS_SERVE],
1378                proof: Vec::new(),
1379            }))
1380        );
1381        assert_eq!(provider.snap_storage_ranges_remaining(), 1);
1382        assert_eq!(provider.snap_state_range_resolutions(), 1);
1383    }
1384
1385    #[tokio::test]
1386    async fn snap_storage_range_proves_finite_limit_from_zero_origin() {
1387        let provider = MockEthProvider::default();
1388        let hash = B256::repeat_byte(0x01);
1389        let proof = vec![Bytes::from_static(&[0xcc])];
1390        // More entries exist beyond `limit_hash`, so the cursor stopped at the hash limit
1391        // rather than exhausting the trie -- a proof is required even though origin is zero.
1392        provider.push_snap_storage_range(vec![(hash, U256::from(1))], RangeEnd::HashLimit);
1393        provider.set_snap_storage_proof(Some(proof.clone()));
1394
1395        let handler = snap_handler(provider.clone());
1396        let (response, rx) = oneshot::channel();
1397        handler.on_snap_request(
1398            PeerId::default(),
1399            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1400                request_id: 5,
1401                root_hash: B256::ZERO,
1402                account_hashes: vec![B256::repeat_byte(0x03)],
1403                starting_hash: B256::ZERO.into(),
1404                limit_hash: B256::repeat_byte(0x20).into(),
1405                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1406            }),
1407            response,
1408        );
1409
1410        assert_eq!(
1411            rx.await.unwrap(),
1412            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1413                request_id: 5,
1414                slots: vec![vec![StorageData { hash, data: Bytes::from_static(&[0x01]) }]],
1415                proof,
1416            }))
1417        );
1418    }
1419
1420    #[tokio::test]
1421    async fn snap_storage_ranges_are_entirely_empty_when_an_account_is_missing() {
1422        let provider = MockEthProvider::default();
1423        provider.push_missing_snap_storage_account();
1424        provider.push_snap_storage_range(
1425            vec![(B256::repeat_byte(0x01), U256::from(1))],
1426            RangeEnd::Exhausted,
1427        );
1428        let missing_account = B256::repeat_byte(0x01);
1429        let valid_account = B256::repeat_byte(0x02);
1430
1431        let handler = snap_handler(provider.clone());
1432        let (response, rx) = oneshot::channel();
1433        handler.on_snap_request(
1434            PeerId::default(),
1435            SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1436                request_id: 6,
1437                root_hash: B256::ZERO,
1438                account_hashes: vec![missing_account, valid_account],
1439                starting_hash: B256::ZERO.into(),
1440                limit_hash: B256::repeat_byte(0xff).into(),
1441                response_bytes: SOFT_RESPONSE_LIMIT as u64,
1442            }),
1443            response,
1444        );
1445
1446        assert_eq!(
1447            rx.await.unwrap(),
1448            Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1449                request_id: 6,
1450                slots: Vec::new(),
1451                proof: Vec::new(),
1452            }))
1453        );
1454        // The valid account's queued range is never consumed: the response bails out at the
1455        // first missing account instead of skipping it and shifting later positions.
1456        assert_eq!(provider.snap_storage_ranges_remaining(), 1);
1457    }
1458}