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 async fn snap_account_range_response_encodes_accounts_and_proof() {
1229 let provider = MockEthProvider::default();
1230 let first_hash = B256::repeat_byte(0x01);
1231 let second_hash = B256::repeat_byte(0x02);
1232 let storage_root = B256::repeat_byte(0x11);
1233 let code_hash = B256::repeat_byte(0x22);
1234 let proof = vec![Bytes::from_static(&[0xaa])];
1235 provider.set_snap_account_range(
1236 vec![
1237 (
1238 first_hash,
1239 Account { nonce: 1, balance: U256::from(2), bytecode_hash: Some(code_hash) },
1240 ),
1241 (second_hash, Account { nonce: 3, balance: U256::from(4), bytecode_hash: None }),
1242 ],
1243 RangeEnd::HashLimit,
1246 );
1247 provider.set_snap_storage_root(first_hash, storage_root);
1248 provider.set_snap_storage_root(second_hash, EMPTY_ROOT_HASH);
1249 provider.set_snap_account_proof(Some(proof.clone()));
1250
1251 let mut full_body = vec![0xf8, 0x44, 0x01, 0x02, 0xa0];
1252 full_body.extend_from_slice(storage_root.as_slice());
1253 full_body.push(0xa0);
1254 full_body.extend_from_slice(code_hash.as_slice());
1255 let empty_body = Bytes::from_static(&[0xc4, 0x03, 0x04, 0x80, 0x80]);
1256
1257 let handler = snap_handler(provider.clone());
1258 let (response, rx) = oneshot::channel();
1259 handler.on_snap_request(
1260 PeerId::default(),
1261 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1262 request_id: 1,
1263 root_hash: B256::ZERO,
1264 starting_hash: B256::ZERO,
1265 limit_hash: B256::repeat_byte(0xff),
1266 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1267 }),
1268 response,
1269 );
1270
1271 assert_eq!(
1272 rx.await.unwrap(),
1273 Ok(SnapResponse::AccountRange(AccountRangeMessage {
1274 request_id: 1,
1275 accounts: vec![
1276 AccountData { hash: first_hash, body: full_body.into() },
1277 AccountData { hash: second_hash, body: empty_body },
1278 ],
1279 proof,
1280 }))
1281 );
1282 assert_eq!(provider.snap_state_range_resolutions(), 1);
1283 }
1284
1285 #[tokio::test]
1286 async fn snap_account_range_response_skips_proof_for_exhausted_zero_origin_range() {
1287 let provider = MockEthProvider::default();
1288 let hash = B256::repeat_byte(0x01);
1289 provider.set_snap_account_range(
1290 vec![(hash, Account { nonce: 1, balance: U256::from(2), bytecode_hash: None })],
1291 RangeEnd::Exhausted,
1292 );
1293 provider.set_snap_storage_root(hash, EMPTY_ROOT_HASH);
1294 provider.set_snap_account_proof(Some(vec![Bytes::from_static(&[0xaa])]));
1297
1298 let handler = snap_handler(provider.clone());
1299 let (response, rx) = oneshot::channel();
1300 handler.on_snap_request(
1301 PeerId::default(),
1302 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1303 request_id: 1,
1304 root_hash: B256::ZERO,
1305 starting_hash: B256::ZERO,
1306 limit_hash: B256::repeat_byte(0xff),
1307 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1308 }),
1309 response,
1310 );
1311
1312 let Ok(SnapResponse::AccountRange(AccountRangeMessage { accounts, proof, .. })) =
1313 rx.await.unwrap()
1314 else {
1315 panic!("expected an account range response");
1316 };
1317 assert_eq!(accounts.len(), 1);
1318 assert!(proof.is_empty(), "a zero-origin, exhausted range must not carry a boundary proof");
1319 }
1320
1321 #[tokio::test]
1322 async fn snap_storage_range_response_encodes_values_and_proof() {
1323 let provider = MockEthProvider::default();
1324 let first_hash = B256::repeat_byte(0x01);
1325 let second_hash = B256::repeat_byte(0x02);
1326 let origin = B256::repeat_byte(0x10);
1327 let proof = vec![Bytes::from_static(&[0xbb])];
1328 provider.push_snap_storage_range(
1329 vec![(first_hash, U256::from(0x0102)), (second_hash, U256::from(0xff))],
1330 RangeEnd::Exhausted,
1331 );
1332 provider.set_snap_storage_proof(Some(proof.clone()));
1333
1334 let handler = snap_handler(provider.clone());
1335 let (response, rx) = oneshot::channel();
1336 handler.on_snap_request(
1337 PeerId::default(),
1338 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1339 request_id: 2,
1340 root_hash: B256::ZERO,
1341 account_hashes: vec![B256::repeat_byte(0x03)],
1342 starting_hash: origin.into(),
1343 limit_hash: B256::repeat_byte(0xff).into(),
1344 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1345 }),
1346 response,
1347 );
1348
1349 assert_eq!(
1350 rx.await.unwrap(),
1351 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1352 request_id: 2,
1353 slots: vec![vec![
1354 StorageData { hash: first_hash, data: Bytes::from_static(&[0x82, 0x01, 0x02]) },
1355 StorageData { hash: second_hash, data: Bytes::from_static(&[0x81, 0xff]) },
1356 ]],
1357 proof,
1358 }))
1359 );
1360 assert_eq!(provider.snap_storage_range_requests()[0].1, origin);
1361 assert_eq!(provider.snap_state_range_resolutions(), 1);
1362 }
1363
1364 #[tokio::test]
1365 async fn snap_storage_ranges_only_bound_the_first_account() {
1366 let provider = MockEthProvider::default();
1367 provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1368 provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1369 let first_account = B256::repeat_byte(0x01);
1370 let second_account = B256::repeat_byte(0x02);
1371 let origin = B256::ZERO;
1372 let limit = B256::repeat_byte(0x22);
1373
1374 let handler = snap_handler(provider.clone());
1375 let (response, rx) = oneshot::channel();
1376 handler.on_snap_request(
1377 PeerId::default(),
1378 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1379 request_id: 3,
1380 root_hash: B256::ZERO,
1381 account_hashes: vec![first_account, second_account],
1382 starting_hash: origin.into(),
1383 limit_hash: limit.into(),
1384 response_bytes: 1_000,
1385 }),
1386 response,
1387 );
1388
1389 assert_eq!(
1390 rx.await.unwrap(),
1391 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1392 request_id: 3,
1393 slots: vec![Vec::new(), Vec::new()],
1394 proof: Vec::new(),
1395 }))
1396 );
1397 assert_eq!(
1398 provider.snap_storage_range_requests(),
1399 vec![
1400 (first_account, origin, limit, 1_000),
1401 (second_account, B256::ZERO, B256::repeat_byte(0xff), 1_000),
1402 ]
1403 );
1404 assert_eq!(provider.snap_state_range_resolutions(), 1);
1405 }
1406
1407 #[tokio::test]
1408 async fn snap_byte_codes_response_preserves_found_code_order() {
1409 let provider = MockEthProvider::default();
1410 let code = Bytes::from_static(&[0x60, 0x00]);
1411 let code_hash = keccak256(&code);
1412 let later_code = Bytes::from_static(&[0x60, 0x01]);
1413 let later_code_hash = keccak256(&later_code);
1414 provider.add_account(
1415 Address::repeat_byte(0x01),
1416 ExtendedAccount::new(1, U256::ZERO).with_bytecode(code.clone()),
1417 );
1418 provider.add_account(
1419 Address::repeat_byte(0x02),
1420 ExtendedAccount::new(1, U256::ZERO).with_bytecode(later_code),
1421 );
1422
1423 let handler = snap_handler(provider);
1424 let (response, rx) = oneshot::channel();
1425 handler.on_snap_request(
1426 PeerId::default(),
1427 SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1428 request_id: 3,
1429 hashes: vec![KECCAK_EMPTY, B256::repeat_byte(0xff), code_hash, later_code_hash],
1430 response_bytes: 1,
1431 }),
1432 response,
1433 );
1434
1435 assert_eq!(
1436 rx.await.unwrap(),
1437 Ok(SnapResponse::ByteCodes(ByteCodesMessage {
1438 request_id: 3,
1439 codes: vec![Bytes::new(), code],
1440 }))
1441 );
1442 }
1443
1444 #[tokio::test]
1445 async fn snap_storage_ranges_limit_account_lookups() {
1446 let provider = MockEthProvider::default();
1447 for _ in 0..=MAX_STORAGE_RANGE_ACCOUNTS_SERVE {
1448 provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1449 }
1450 let handler = snap_handler(provider.clone());
1451 let (response, rx) = oneshot::channel();
1452 handler.on_snap_request(
1453 PeerId::default(),
1454 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1455 request_id: 4,
1456 root_hash: B256::ZERO,
1457 account_hashes: vec![B256::ZERO; MAX_STORAGE_RANGE_ACCOUNTS_SERVE + 1],
1458 starting_hash: B256::ZERO.into(),
1459 limit_hash: B256::repeat_byte(0xff).into(),
1460 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1461 }),
1462 response,
1463 );
1464
1465 assert_eq!(
1466 rx.await.unwrap(),
1467 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1468 request_id: 4,
1469 slots: vec![Vec::new(); MAX_STORAGE_RANGE_ACCOUNTS_SERVE],
1470 proof: Vec::new(),
1471 }))
1472 );
1473 assert_eq!(provider.snap_storage_ranges_remaining(), 1);
1474 assert_eq!(provider.snap_state_range_resolutions(), 1);
1475 }
1476
1477 #[tokio::test]
1478 async fn snap_storage_range_proves_finite_limit_from_zero_origin() {
1479 let provider = MockEthProvider::default();
1480 let hash = B256::repeat_byte(0x01);
1481 let proof = vec![Bytes::from_static(&[0xcc])];
1482 provider.push_snap_storage_range(vec![(hash, U256::from(1))], RangeEnd::HashLimit);
1485 provider.set_snap_storage_proof(Some(proof.clone()));
1486
1487 let handler = snap_handler(provider.clone());
1488 let (response, rx) = oneshot::channel();
1489 handler.on_snap_request(
1490 PeerId::default(),
1491 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1492 request_id: 5,
1493 root_hash: B256::ZERO,
1494 account_hashes: vec![B256::repeat_byte(0x03)],
1495 starting_hash: B256::ZERO.into(),
1496 limit_hash: B256::repeat_byte(0x20).into(),
1497 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1498 }),
1499 response,
1500 );
1501
1502 assert_eq!(
1503 rx.await.unwrap(),
1504 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1505 request_id: 5,
1506 slots: vec![vec![StorageData { hash, data: Bytes::from_static(&[0x01]) }]],
1507 proof,
1508 }))
1509 );
1510 }
1511
1512 #[tokio::test]
1513 async fn snap_storage_ranges_are_entirely_empty_when_an_account_is_missing() {
1514 let provider = MockEthProvider::default();
1515 provider.push_missing_snap_storage_account();
1516 provider.push_snap_storage_range(
1517 vec![(B256::repeat_byte(0x01), U256::from(1))],
1518 RangeEnd::Exhausted,
1519 );
1520 let missing_account = B256::repeat_byte(0x01);
1521 let valid_account = B256::repeat_byte(0x02);
1522
1523 let handler = snap_handler(provider.clone());
1524 let (response, rx) = oneshot::channel();
1525 handler.on_snap_request(
1526 PeerId::default(),
1527 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1528 request_id: 6,
1529 root_hash: B256::ZERO,
1530 account_hashes: vec![missing_account, valid_account],
1531 starting_hash: B256::ZERO.into(),
1532 limit_hash: B256::repeat_byte(0xff).into(),
1533 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1534 }),
1535 response,
1536 );
1537
1538 assert_eq!(
1539 rx.await.unwrap(),
1540 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1541 request_id: 6,
1542 slots: Vec::new(),
1543 proof: Vec::new(),
1544 }))
1545 );
1546 assert_eq!(provider.snap_storage_ranges_remaining(), 1);
1549 }
1550}