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