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