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