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