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