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