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_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 collections::VecDeque,
37 future::Future,
38 pin::Pin,
39 task::{Context, Poll},
40 time::Duration,
41};
42use tokio::{
43 sync::{mpsc::Receiver, oneshot},
44 task::JoinHandle,
45 time::{sleep_until, Instant, Sleep},
46};
47use tokio_stream::wrappers::ReceiverStream;
48use tracing::{debug, error, Span};
49
50pub const MAX_RECEIPTS_SERVE: usize = 1024;
56
57pub const MAX_HEADERS_SERVE: usize = 1024;
61
62pub const MAX_BODIES_SERVE: usize = 1024;
67
68pub const MAX_BLOCK_ACCESS_LISTS_SERVE: usize = 1024;
72
73pub const MAX_CELLS_SERVE: usize = 1024;
77
78pub const MAX_BYTE_CODES_SERVE: usize = 1024;
82
83pub const MAX_STORAGE_RANGE_ACCOUNTS_SERVE: usize = 1024;
85
86pub const SOFT_RESPONSE_LIMIT: usize = 2 * 1024 * 1024;
88
89const MAX_CONCURRENT_STATE_RANGE_REQUESTS: usize = 2;
94
95const MAX_QUEUED_STATE_RANGE_REQUESTS: usize = 16;
100
101const STATE_RANGE_REQUEST_QUEUE_TIMEOUT: Duration = Duration::from_millis(500);
106
107const MAX_HEADERS_RESERVE: usize = 64;
113
114const MAX_BLOCKS_RESERVE: usize = 16;
120
121#[derive(Debug)]
125#[must_use = "Manager does nothing unless polled."]
126pub struct EthRequestHandler<C, N: NetworkPrimitives = EthNetworkPrimitives> {
127 client: C,
129 blob_store: Box<dyn BlobStore>,
131 incoming_requests: ReceiverStream<IncomingEthRequest<N>>,
133 metrics: EthRequestHandlerMetrics,
135 state_range_requests: StateRangeRequests,
137}
138
139impl<C, N: NetworkPrimitives> EthRequestHandler<C, N> {
141 pub fn new(client: C, incoming: Receiver<IncomingEthRequest<N>>) -> Self {
143 Self {
144 client,
145 blob_store: Box::<NoopBlobStore>::default(),
146 incoming_requests: ReceiverStream::new(incoming),
147 metrics: Default::default(),
148 state_range_requests: Default::default(),
149 }
150 }
151
152 pub fn with_blob_store(mut self, blob_store: Box<dyn BlobStore>) -> Self {
154 self.blob_store = blob_store;
155 self
156 }
157}
158
159impl<C, N> EthRequestHandler<C, N>
160where
161 N: NetworkPrimitives,
162 C: BlockReader,
163{
164 fn get_headers_response(&self, request: GetBlockHeaders) -> Vec<C::Header> {
166 let GetBlockHeaders { start_block, limit, skip, direction } = request;
167
168 let mut headers = Vec::new();
169
170 let mut block: BlockHashOrNumber = match start_block {
171 BlockHashOrNumber::Hash(start) => start.into(),
172 BlockHashOrNumber::Number(num) => {
173 let Some(hash) = self.client.block_hash(num).unwrap_or_default() else {
174 return headers
175 };
176 hash.into()
177 }
178 };
179
180 let skip = skip as u64;
181 let mut total_bytes = 0;
182
183 for _ in 0..limit {
184 if let Some(header) = self.client.header_by_hash_or_number(block).unwrap_or_default() {
185 let number = header.number();
186 let parent_hash = header.parent_hash();
187 let len = header.length();
188
189 if headers.is_empty() {
190 let count = limit.min(MAX_HEADERS_SERVE as u64) as usize;
191 headers.reserve(response_reserve_hint(count, len, MAX_HEADERS_RESERVE));
192 }
193
194 total_bytes += len;
195 headers.push(header);
196
197 if headers.len() >= MAX_HEADERS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
198 break
199 }
200
201 match direction {
202 HeadersDirection::Rising => {
203 if let Some(next) = number.checked_add(1).and_then(|n| n.checked_add(skip))
204 {
205 block = next.into()
206 } else {
207 break
208 }
209 }
210 HeadersDirection::Falling => {
211 if skip > 0 {
212 if let Some(next) =
215 number.checked_sub(1).and_then(|num| num.checked_sub(skip))
216 {
217 block = next.into()
218 } else {
219 break
220 }
221 } else {
222 block = parent_hash.into()
223 }
224 }
225 }
226 } else {
227 break
228 }
229 }
230
231 headers
232 }
233
234 fn on_headers_request(
235 &self,
236 _peer_id: PeerId,
237 request: GetBlockHeaders,
238 response: oneshot::Sender<RequestResult<BlockHeaders<C::Header>>>,
239 ) {
240 self.metrics.eth_headers_requests_received_total.increment(1);
241 let headers = self.get_headers_response(request);
242 let _ = response.send(Ok(BlockHeaders(headers)));
243 }
244
245 fn on_bodies_request(
246 &self,
247 _peer_id: PeerId,
248 request: GetBlockBodies,
249 response: oneshot::Sender<RequestResult<BlockBodies<<C::Block as Block>::Body>>>,
250 ) {
251 self.metrics.eth_bodies_requests_received_total.increment(1);
252 let count = request.0.len();
253 let mut bodies = Vec::new();
254
255 let mut total_bytes = 0;
256
257 for hash in request {
258 if let Some(block) = self.client.block_by_hash(hash).unwrap_or_default() {
259 let body = block.into_body();
260 let len = body.length();
261 if bodies.is_empty() {
262 bodies.reserve(response_reserve_hint(count, len, MAX_BLOCKS_RESERVE));
263 }
264 total_bytes += len;
265 bodies.push(body);
266
267 if bodies.len() >= MAX_BODIES_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
268 break
269 }
270 } else {
271 break
272 }
273 }
274
275 let _ = response.send(Ok(BlockBodies(bodies)));
276 }
277
278 fn on_node_data_request(
284 &self,
285 _peer_id: PeerId,
286 _request: GetNodeData,
287 response: oneshot::Sender<RequestResult<NodeData>>,
288 ) {
289 self.metrics.eth_node_data_requests_received_total.increment(1);
290 let _ = response.send(Ok(NodeData(vec![])));
291 }
292
293 fn on_receipts_request(
294 &self,
295 _peer_id: PeerId,
296 request: GetReceipts,
297 response: oneshot::Sender<RequestResult<Receipts<C::Receipt>>>,
298 ) {
299 self.metrics.eth_receipts_requests_received_total.increment(1);
300
301 let receipts = self.get_receipts_response(request, |receipts_by_block| {
302 receipts_by_block.into_iter().map(ReceiptWithBloom::from).collect::<Vec<_>>()
303 });
304
305 let _ = response.send(Ok(Receipts(receipts)));
306 }
307
308 fn on_receipts69_request(
309 &self,
310 _peer_id: PeerId,
311 request: GetReceipts,
312 response: oneshot::Sender<RequestResult<Receipts69<C::Receipt>>>,
313 ) {
314 self.metrics.eth_receipts_requests_received_total.increment(1);
315
316 let receipts = self.get_receipts_response(request, |receipts_by_block| {
317 receipts_by_block
319 });
320
321 let _ = response.send(Ok(Receipts69(receipts)));
322 }
323
324 fn on_receipts70_request(
333 &self,
334 _peer_id: PeerId,
335 request: GetReceipts70,
336 response: oneshot::Sender<RequestResult<Receipts70<C::Receipt>>>,
337 ) {
338 self.metrics.eth_receipts_requests_received_total.increment(1);
339
340 let GetReceipts70 { first_block_receipt_index, block_hashes } = request;
341
342 let count = block_hashes.len();
343 let mut receipts = Vec::new();
344 let mut total_bytes = 0usize;
345 let mut last_block_incomplete = false;
346
347 let max_receipts_size = MAX_MESSAGE_SIZE -
350 (1 + u64::MAX.length() + false.length() + 3 * length_of_length(MAX_MESSAGE_SIZE));
351
352 for (idx, hash) in block_hashes.into_iter().enumerate() {
353 if idx >= MAX_RECEIPTS_SERVE {
354 break
355 }
356
357 let Some(mut block_receipts) =
358 self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
359 else {
360 break
361 };
362
363 if idx == 0 && first_block_receipt_index > 0 {
364 let skip = first_block_receipt_index as usize;
365 if skip >= block_receipts.len() {
366 block_receipts.clear();
367 } else {
368 block_receipts.drain(0..skip);
369 }
370 }
371
372 let block_size = block_receipts.length();
373 if receipts.is_empty() {
374 receipts.reserve(response_reserve_hint(count, block_size, MAX_BLOCKS_RESERVE));
375 }
376
377 if total_bytes + block_size <= SOFT_RESPONSE_LIMIT {
378 total_bytes += block_size;
379 receipts.push(block_receipts);
380 continue;
381 }
382
383 let remaining = block_receipts.len();
387 let mut partial_block = Vec::new();
388 for receipt in block_receipts {
389 let receipt_size = receipt.length();
390 if total_bytes + receipt_size > max_receipts_size {
391 break;
392 }
393 total_bytes += receipt_size;
394 partial_block.push(receipt);
395 }
396
397 if !partial_block.is_empty() {
400 last_block_incomplete = partial_block.len() < remaining;
401 receipts.push(partial_block);
402 }
403 break;
404 }
405
406 let _ = response.send(Ok(Receipts70 { last_block_incomplete, receipts }));
407 }
408
409 #[inline]
410 fn get_receipts_response<T, F>(&self, request: GetReceipts, transform_fn: F) -> Vec<Vec<T>>
411 where
412 F: Fn(Vec<C::Receipt>) -> Vec<T>,
413 T: Encodable,
414 {
415 let count = request.0.len();
416 let mut receipts = Vec::new();
417 let mut total_bytes = 0;
418
419 for hash in request {
420 if let Some(receipts_by_block) =
421 self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
422 {
423 let transformed_receipts = transform_fn(receipts_by_block);
424 let len = transformed_receipts.length();
425 if receipts.is_empty() {
426 receipts.reserve(response_reserve_hint(count, len, MAX_BLOCKS_RESERVE));
427 }
428 total_bytes += len;
429 receipts.push(transformed_receipts);
430
431 if receipts.len() >= MAX_RECEIPTS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT {
432 break
433 }
434 } else {
435 break
436 }
437 }
438
439 receipts
440 }
441
442 fn on_cells_request(
443 &self,
444 _peer_id: PeerId,
445 request: GetCells,
446 response: oneshot::Sender<RequestResult<Cells>>,
447 ) {
448 let mut cells_response = Cells { cell_mask: request.cell_mask, ..Default::default() };
449 let mut total_bytes = 0;
450 let cell_mask = request.cell_mask();
451
452 for hash in request.hashes.into_iter().take(MAX_CELLS_SERVE) {
453 let Some(cells) = self.blob_store.get_cells(hash, cell_mask).unwrap_or_default() else {
454 continue;
455 };
456
457 total_bytes += hash.length() + cells.length();
458 cells_response.hashes.push(hash);
459 cells_response.cells.push(cells);
460
461 if total_bytes > SOFT_RESPONSE_LIMIT {
462 break
463 }
464 }
465
466 let _ = response.send(Ok(cells_response));
467 }
468}
469
470fn response_reserve_hint(count: usize, first_len: usize, max: usize) -> usize {
475 SOFT_RESPONSE_LIMIT.div_ceil(first_len.max(1)).min(count).min(max)
476}
477
478impl<C, N> EthRequestHandler<C, N>
479where
480 N: NetworkPrimitives,
481 C: BalProvider,
482{
483 fn on_block_access_lists_request(
488 &self,
489 _peer_id: PeerId,
490 mut request: GetBlockAccessLists,
491 response: oneshot::Sender<RequestResult<BlockAccessLists>>,
492 ) {
493 self.metrics.eth_block_access_lists_requests_received_total.increment(1);
494 request.0.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
495
496 let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(SOFT_RESPONSE_LIMIT);
497 let access_lists =
498 self.client.get_bals_by_hashes_with_limit(&request.0, limit).unwrap_or_default();
499 let _ = response.send(Ok(BlockAccessLists(access_lists)));
500 }
501}
502
503impl<C, N> EthRequestHandler<C, N>
504where
505 N: NetworkPrimitives,
506 C: BalProvider + StateProviderFactory + StateRangeProviderFactory + Clone + 'static,
507{
508 fn on_snap_request(
517 &mut self,
518 peer_id: PeerId,
519 request: SnapProtocolMessage,
520 response: oneshot::Sender<RequestResult<SnapResponse>>,
521 ) {
522 self.metrics.snap_requests_received_total.increment(1);
523
524 let result = match request {
525 SnapProtocolMessage::GetAccountRange(req) => {
526 return self.on_state_range_request(
527 peer_id,
528 StateRangeRequest::Account(req),
529 response,
530 )
531 }
532 SnapProtocolMessage::GetStorageRanges(mut req) => {
533 req.account_hashes.truncate(MAX_STORAGE_RANGE_ACCOUNTS_SERVE);
536 req.account_hashes.shrink_to_fit();
537 return self.on_state_range_request(
538 peer_id,
539 StateRangeRequest::Storage(req),
540 response,
541 )
542 }
543 SnapProtocolMessage::GetByteCodes(req) => {
544 let codes = self
545 .get_byte_codes_response(&req.hashes, req.response_bytes as usize)
546 .unwrap_or_else(|error| {
547 tracing::debug!(target: "net::snap", %error, "failed to serve bytecodes");
548 Vec::new()
549 });
550 Ok(SnapResponse::ByteCodes(ByteCodesMessage { request_id: req.request_id, codes }))
551 }
552 SnapProtocolMessage::GetBlockAccessLists(mut req) => {
553 req.block_hashes.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
554 let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(
555 (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT),
556 );
557 let block_access_lists = self
558 .client
559 .get_bals_by_hashes_with_limit(&req.block_hashes, limit)
560 .unwrap_or_default();
561 Ok(SnapResponse::BlockAccessLists(BlockAccessListsMessage {
562 request_id: req.request_id,
563 block_access_lists: BlockAccessLists(block_access_lists),
564 }))
565 }
566 _ => Err(RequestError::BadResponse),
569 };
570
571 let _ = response.send(result);
572 }
573
574 fn on_state_range_request(
576 &mut self,
577 peer_id: PeerId,
578 request: StateRangeRequest,
579 response: oneshot::Sender<RequestResult<SnapResponse>>,
580 ) {
581 let incoming =
582 IncomingStateRangeRequest { peer_id, request, response, received: Instant::now() };
583 self.state_range_requests.admit(incoming, &self.client, &self.metrics);
584 }
585
586 fn get_byte_codes_response(
589 &self,
590 hashes: &[B256],
591 response_bytes: usize,
592 ) -> ProviderResult<Vec<Bytes>> {
593 let state = self.client.latest()?;
594 let response_bytes = response_bytes.min(SOFT_RESPONSE_LIMIT);
595
596 let mut codes = Vec::new();
597 let mut total_bytes = 0;
598 for hash in hashes.iter().take(MAX_BYTE_CODES_SERVE) {
599 let bytes = if *hash == KECCAK_EMPTY {
600 Bytes::new()
601 } else {
602 match state.bytecode_by_hash(hash)? {
603 Some(bytecode) => bytecode.original_bytes(),
604 None => continue,
605 }
606 };
607 total_bytes += bytes.len();
608 codes.push(bytes);
609
610 if total_bytes > response_bytes {
611 break
612 }
613 }
614 Ok(codes)
615 }
616}
617
618fn boundary_proof_keys<T>(origin: B256, last: Option<&(B256, T)>) -> Vec<B256> {
620 match last {
621 Some((last, _)) => vec![origin, *last],
622 None => vec![origin],
623 }
624}
625
626impl<C, N> Future for EthRequestHandler<C, N>
630where
631 N: NetworkPrimitives,
632 C: BalProvider
633 + StateProviderFactory
634 + StateRangeProviderFactory
635 + BlockReader<Block = N::Block, Receipt = N::Receipt>
636 + HeaderProvider<Header = N::BlockHeader>
637 + Clone
638 + Unpin
639 + 'static,
640{
641 type Output = ();
642
643 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
644 let this = self.get_mut();
645
646 this.state_range_requests.poll(cx, &this.client, &this.metrics);
649
650 let mut acc = Duration::ZERO;
651 let maybe_more_incoming_requests = metered_poll_nested_stream_with_budget!(
652 acc,
653 "net::eth",
654 "Incoming eth requests stream",
655 DEFAULT_BUDGET_TRY_DRAIN_DOWNLOADERS,
656 this.incoming_requests.poll_next_unpin(cx),
657 |incoming| {
658 match incoming {
659 IncomingEthRequest::GetBlockHeaders { peer_id, request, response } => {
660 this.on_headers_request(peer_id, request, response)
661 }
662 IncomingEthRequest::GetBlockBodies { peer_id, request, response } => {
663 this.on_bodies_request(peer_id, request, response)
664 }
665 IncomingEthRequest::GetNodeData { peer_id, request, response } => {
666 this.on_node_data_request(peer_id, request, response)
667 }
668 IncomingEthRequest::GetReceipts { peer_id, request, response } => {
669 this.on_receipts_request(peer_id, request, response)
670 }
671 IncomingEthRequest::GetReceipts69 { peer_id, request, response } => {
672 this.on_receipts69_request(peer_id, request, response)
673 }
674 IncomingEthRequest::GetReceipts70 { peer_id, request, response } => {
675 this.on_receipts70_request(peer_id, request, response)
676 }
677 IncomingEthRequest::GetBlockAccessLists { peer_id, request, response } => {
678 this.on_block_access_lists_request(peer_id, request, response)
679 }
680 IncomingEthRequest::GetCells { peer_id, request, response } => {
681 this.on_cells_request(peer_id, request, response)
682 }
683 IncomingEthRequest::GetSnap { peer_id, request, response } => {
684 this.on_snap_request(peer_id, request, response)
685 }
686 }
687 },
688 );
689
690 this.metrics.acc_duration_poll_eth_req_handler.set(acc.as_secs_f64());
691
692 this.state_range_requests.poll(cx, &this.client, &this.metrics);
695
696 if maybe_more_incoming_requests {
698 cx.waker().wake_by_ref();
700 }
701
702 Poll::Pending
703 }
704}
705
706#[derive(Debug, Default)]
715struct StateRangeRequests {
716 tasks: Vec<(PeerId, JoinHandle<()>)>,
718 queue: VecDeque<IncomingStateRangeRequest>,
720 queue_timeout: Option<Pin<Box<Sleep>>>,
722}
723
724impl StateRangeRequests {
725 fn admit<C>(
730 &mut self,
731 incoming: IncomingStateRangeRequest,
732 client: &C,
733 metrics: &EthRequestHandlerMetrics,
734 ) where
735 C: StateRangeProviderFactory + Clone + Send + 'static,
736 {
737 if incoming.response.is_closed() {
738 return
739 }
740 if self.can_start(incoming.peer_id) {
741 return self.spawn(incoming, client)
742 }
743
744 self.remove_stale(metrics);
745 if self.queue.len() >= MAX_QUEUED_STATE_RANGE_REQUESTS ||
746 self.queue.iter().any(|queued| queued.peer_id == incoming.peer_id)
747 {
748 debug!(
749 target: "net::snap",
750 peer_id=?incoming.peer_id,
751 queued=self.queue.len(),
752 "no room to queue state range request"
753 );
754 return incoming.reply_empty(metrics)
755 }
756 self.queue.push_back(incoming);
757 }
758
759 fn can_start(&self, peer_id: PeerId) -> bool {
761 self.tasks.len() < MAX_CONCURRENT_STATE_RANGE_REQUESTS &&
762 self.tasks.iter().all(|(running, _)| *running != peer_id)
763 }
764
765 fn spawn<C>(&mut self, incoming: IncomingStateRangeRequest, client: &C)
767 where
768 C: StateRangeProviderFactory + Clone + Send + 'static,
769 {
770 let IncomingStateRangeRequest { peer_id, request, response, .. } = incoming;
771 let client = client.clone();
772 let span = Span::current();
773 let task = tokio::task::spawn_blocking(move || {
774 let _guard = span.enter();
775 let empty = request.empty_response();
776 let result = request.serve(&client).unwrap_or_else(|error| {
777 debug!(target: "net::snap", %error, "failed to serve state range");
778 empty
779 });
780 let _ = response.send(Ok(result));
781 });
782 self.tasks.push((peer_id, task));
783 }
784
785 fn remove_stale(&mut self, metrics: &EthRequestHandlerMetrics) {
788 let mut index = 0;
789 while index < self.queue.len() {
790 let queued = &self.queue[index];
791 if queued.response.is_closed() {
792 self.queue.remove(index);
793 } else if queued.received.elapsed() < STATE_RANGE_REQUEST_QUEUE_TIMEOUT {
794 index += 1;
795 } else if let Some(incoming) = self.queue.remove(index) {
796 debug!(
797 target: "net::snap",
798 peer_id=?incoming.peer_id,
799 "state range request timed out in queue"
800 );
801 incoming.reply_empty(metrics);
802 }
803 }
804 }
805
806 fn poll<C>(&mut self, cx: &mut Context<'_>, client: &C, metrics: &EthRequestHandlerMetrics)
810 where
811 C: StateRangeProviderFactory + Clone + Send + 'static,
812 {
813 loop {
816 self.remove_stale(metrics);
817 self.tasks.retain_mut(|(_, task)| match Pin::new(task).poll(cx) {
818 Poll::Pending => true,
819 Poll::Ready(result) => {
820 if let Err(error) = result {
821 error!(target: "net::snap", %error, "state range task failed");
822 }
823 false
824 }
825 });
826
827 let mut spawned = false;
828 let mut index = 0;
829 while index < self.queue.len() && self.tasks.len() < MAX_CONCURRENT_STATE_RANGE_REQUESTS
830 {
831 if !self.can_start(self.queue[index].peer_id) {
833 index += 1;
834 } else if let Some(incoming) = self.queue.remove(index) {
835 self.spawn(incoming, client);
836 spawned = true;
837 }
838 }
839 if !spawned && !self.poll_queue_timeout(cx) {
840 break
841 }
842 }
843 }
844
845 fn poll_queue_timeout(&mut self, cx: &mut Context<'_>) -> bool {
847 let Some(oldest) = self.queue.front() else {
848 self.queue_timeout = None;
849 return false
850 };
851 let deadline = oldest.received + STATE_RANGE_REQUEST_QUEUE_TIMEOUT;
852 let timeout = self.queue_timeout.get_or_insert_with(|| Box::pin(sleep_until(deadline)));
853 timeout.as_mut().reset(deadline);
854 timeout.as_mut().poll(cx).is_ready()
855 }
856}
857
858#[derive(Debug)]
860struct IncomingStateRangeRequest {
861 peer_id: PeerId,
863 request: StateRangeRequest,
865 response: oneshot::Sender<RequestResult<SnapResponse>>,
867 received: Instant,
869}
870
871impl IncomingStateRangeRequest {
872 fn reply_empty(self, metrics: &EthRequestHandlerMetrics) {
874 metrics.snap_state_range_requests_unserved_total.increment(1);
875 let _ = self.response.send(Ok(self.request.empty_response()));
876 }
877}
878
879#[derive(Debug)]
881enum StateRangeRequest {
882 Account(GetAccountRangeMessage),
884 Storage(GetStorageRangesMessage),
886}
887
888impl StateRangeRequest {
889 const fn empty_response(&self) -> SnapResponse {
892 match self {
893 Self::Account(req) => SnapResponse::AccountRange(AccountRangeMessage {
894 request_id: req.request_id,
895 accounts: Vec::new(),
896 proof: Vec::new(),
897 }),
898 Self::Storage(req) => SnapResponse::StorageRanges(StorageRangesMessage {
899 request_id: req.request_id,
900 slots: Vec::new(),
901 proof: Vec::new(),
902 }),
903 }
904 }
905
906 fn serve(self, client: &impl StateRangeProviderFactory) -> ProviderResult<SnapResponse> {
908 match self {
909 Self::Account(req) => {
910 Self::serve_account_range(client, req).map(SnapResponse::AccountRange)
911 }
912 Self::Storage(req) => {
913 Self::serve_storage_ranges(client, req).map(SnapResponse::StorageRanges)
914 }
915 }
916 }
917
918 fn serve_account_range(
925 client: &impl StateRangeProviderFactory,
926 req: GetAccountRangeMessage,
927 ) -> ProviderResult<AccountRangeMessage> {
928 let empty = AccountRangeMessage {
929 request_id: req.request_id,
930 accounts: Vec::new(),
931 proof: Vec::new(),
932 };
933
934 let response_bytes = (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT);
935 let Some(state) = client.state_range_provider(req.root_hash)? else { return Ok(empty) };
936 let RangeResponse { items: accounts, end } =
937 state.account_range(req.starting_hash, req.limit_hash, response_bytes)?;
938
939 let proof = if req.starting_hash.is_zero() && end == RangeEnd::Exhausted {
940 Vec::new()
941 } else {
942 let boundary_keys = boundary_proof_keys(req.starting_hash, accounts.last());
943 state.account_range_proof(&boundary_keys)?
944 };
945
946 let mut account_data = Vec::with_capacity(accounts.len());
947 for (hash, account) in accounts {
948 let storage_root = state.storage_root_by_hash(hash)?;
949 account_data.push(AccountData::from_trie_account(
950 hash,
951 &account.into_trie_account(storage_root),
952 ));
953 }
954
955 Ok(AccountRangeMessage { request_id: req.request_id, accounts: account_data, proof })
956 }
957
958 fn serve_storage_ranges(
965 client: &impl StateRangeProviderFactory,
966 req: GetStorageRangesMessage,
967 ) -> ProviderResult<StorageRangesMessage> {
968 let empty = StorageRangesMessage {
969 request_id: req.request_id,
970 slots: Vec::new(),
971 proof: Vec::new(),
972 };
973 let Some(state) = client.state_range_provider(req.root_hash)? else { return Ok(empty) };
974 let mut slots: Vec<Vec<StorageData>> = Vec::new();
975 let mut proof = Vec::new();
976 let mut remaining_bytes = (req.response_bytes as usize).min(SOFT_RESPONSE_LIMIT);
977
978 for (i, &hashed_address) in
979 req.account_hashes.iter().take(MAX_STORAGE_RANGE_ACCOUNTS_SERVE).enumerate()
980 {
981 if remaining_bytes == 0 && slots.iter().any(|range| !range.is_empty()) {
984 break
985 }
986 let (origin, limit) = if i == 0 {
987 (
988 req.starting_hash.unwrap_or(B256::ZERO),
989 req.limit_hash.unwrap_or(B256::repeat_byte(0xff)),
990 )
991 } else {
992 (B256::ZERO, B256::repeat_byte(0xff))
993 };
994 let Some(RangeResponse { items: account_slots, end }) =
995 state.storage_range(hashed_address, origin, limit, remaining_bytes)?
996 else {
997 return Ok(empty)
998 };
999
1000 remaining_bytes = remaining_bytes.saturating_sub(account_slots.len() * 64);
1001 let last = account_slots.last().map(|(hash, _)| *hash);
1002 let needs_proof = !origin.is_zero() || end != RangeEnd::Exhausted;
1003 slots.push(
1004 account_slots
1005 .into_iter()
1006 .map(|(hash, value)| StorageData::from_value(hash, value))
1008 .collect(),
1009 );
1010
1011 if needs_proof {
1012 let boundary_keys = match last {
1013 Some(last) => vec![origin, last],
1014 None => vec![origin],
1015 };
1016 proof = state.storage_range_proof(hashed_address, &boundary_keys)?;
1017 break
1018 }
1019 }
1020
1021 Ok(StorageRangesMessage { request_id: req.request_id, slots, proof })
1022 }
1023}
1024
1025#[derive(Debug)]
1027pub enum IncomingEthRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
1028 GetBlockHeaders {
1032 peer_id: PeerId,
1034 request: GetBlockHeaders,
1036 response: oneshot::Sender<RequestResult<BlockHeaders<N::BlockHeader>>>,
1038 },
1039 GetBlockBodies {
1043 peer_id: PeerId,
1045 request: GetBlockBodies,
1047 response: oneshot::Sender<RequestResult<BlockBodies<N::BlockBody>>>,
1049 },
1050 GetNodeData {
1054 peer_id: PeerId,
1056 request: GetNodeData,
1058 response: oneshot::Sender<RequestResult<NodeData>>,
1060 },
1061 GetReceipts {
1065 peer_id: PeerId,
1067 request: GetReceipts,
1069 response: oneshot::Sender<RequestResult<Receipts<N::Receipt>>>,
1071 },
1072 GetReceipts69 {
1076 peer_id: PeerId,
1078 request: GetReceipts,
1080 response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
1082 },
1083 GetReceipts70 {
1087 peer_id: PeerId,
1089 request: GetReceipts70,
1091 response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
1093 },
1094 GetBlockAccessLists {
1098 peer_id: PeerId,
1100 request: GetBlockAccessLists,
1102 response: oneshot::Sender<RequestResult<BlockAccessLists>>,
1104 },
1105 GetCells {
1109 peer_id: PeerId,
1111 request: GetCells,
1113 response: oneshot::Sender<RequestResult<Cells>>,
1115 },
1116 GetSnap {
1120 peer_id: PeerId,
1122 request: SnapProtocolMessage,
1124 response: oneshot::Sender<RequestResult<SnapResponse>>,
1126 },
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131 use super::*;
1132 use alloy_consensus::{constants::EMPTY_ROOT_HASH, Header, TxType};
1133 use alloy_eips::{
1134 eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
1135 eip7594::{BlobCellMask, BlobTransactionSidecarVariant, Cell},
1136 };
1137 use alloy_primitives::{keccak256, Address, Log, LogData, TxHash, B128, U256};
1138 use alloy_rlp::{Decodable, Encodable};
1139 use reth_eth_wire::{message::RequestPair, EthMessage, EthStreamInner, EthVersion};
1140 use reth_ethereum_primitives::Receipt;
1141 use reth_primitives_traits::Account;
1142 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1143 use reth_storage_api::noop::NoopProvider;
1144 use reth_transaction_pool::blobstore::{
1145 BlobStoreCleanupStat, BlobStoreError, PooledBlobSidecar,
1146 };
1147 use std::sync::{
1148 atomic::{AtomicUsize, Ordering},
1149 Arc,
1150 };
1151 use test_case::test_case;
1152 use tokio::sync::mpsc;
1153
1154 #[derive(Debug, Default)]
1155 struct CountingBlobStore {
1156 get_cells_calls: Arc<AtomicUsize>,
1157 expected_cell_mask: Option<BlobCellMask>,
1158 }
1159
1160 impl BlobStore for CountingBlobStore {
1161 fn insert(&self, _tx: B256, _data: PooledBlobSidecar) -> Result<(), BlobStoreError> {
1162 Ok(())
1163 }
1164
1165 fn insert_all(&self, _txs: Vec<(B256, PooledBlobSidecar)>) -> Result<(), BlobStoreError> {
1166 Ok(())
1167 }
1168
1169 fn delete(&self, _tx: B256) -> Result<(), BlobStoreError> {
1170 Ok(())
1171 }
1172
1173 fn delete_all(&self, _txs: Vec<B256>) -> Result<(), BlobStoreError> {
1174 Ok(())
1175 }
1176
1177 fn cleanup(&self) -> BlobStoreCleanupStat {
1178 BlobStoreCleanupStat::default()
1179 }
1180
1181 fn get(
1182 &self,
1183 _tx: B256,
1184 ) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError> {
1185 Ok(None)
1186 }
1187
1188 fn contains(&self, _tx: B256) -> Result<bool, BlobStoreError> {
1189 Ok(false)
1190 }
1191
1192 fn get_all(
1193 &self,
1194 _txs: Vec<B256>,
1195 ) -> Result<Vec<(B256, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError> {
1196 Ok(vec![])
1197 }
1198
1199 fn get_exact(
1200 &self,
1201 txs: Vec<B256>,
1202 ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError> {
1203 if txs.is_empty() {
1204 return Ok(vec![])
1205 }
1206
1207 Err(BlobStoreError::MissingSidecar(txs[0]))
1208 }
1209
1210 fn get_by_versioned_hashes_v1(
1211 &self,
1212 versioned_hashes: &[B256],
1213 ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError> {
1214 Ok(vec![None; versioned_hashes.len()])
1215 }
1216
1217 fn get_by_versioned_hashes_v2(
1218 &self,
1219 _versioned_hashes: &[B256],
1220 ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError> {
1221 Ok(None)
1222 }
1223
1224 fn get_by_versioned_hashes_v3(
1225 &self,
1226 versioned_hashes: &[B256],
1227 ) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError> {
1228 Ok(vec![None; versioned_hashes.len()])
1229 }
1230
1231 fn get_by_versioned_hashes_v4(
1232 &self,
1233 versioned_hashes: &[B256],
1234 _cell_mask: BlobCellMask,
1235 ) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
1236 Ok(vec![None; versioned_hashes.len()])
1237 }
1238
1239 fn has_versioned_hashes(
1240 &self,
1241 versioned_hashes: &[B256],
1242 ) -> Result<Vec<bool>, BlobStoreError> {
1243 Ok(vec![false; versioned_hashes.len()])
1244 }
1245
1246 fn get_cells(
1247 &self,
1248 _tx_hash: TxHash,
1249 cell_mask: BlobCellMask,
1250 ) -> Result<Option<Vec<Cell>>, BlobStoreError> {
1251 self.get_cells_calls.fetch_add(1, Ordering::Relaxed);
1252 if let Some(expected) = self.expected_cell_mask {
1253 assert_eq!(cell_mask, expected);
1254 return Ok(Some(vec![Cell::default()]))
1255 }
1256 Ok(None)
1257 }
1258
1259 fn data_size_hint(&self) -> Option<usize> {
1260 Some(0)
1261 }
1262
1263 fn blobs_len(&self) -> usize {
1264 0
1265 }
1266 }
1267
1268 #[tokio::test]
1269 async fn get_cells_request_limits_blob_store_lookups() {
1270 let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1271 let get_cells_calls = Arc::new(AtomicUsize::new(0));
1272 let blob_store = CountingBlobStore {
1273 get_cells_calls: Arc::clone(&get_cells_calls),
1274 ..Default::default()
1275 };
1276 let handler = EthRequestHandler::<NoopProvider>::new(NoopProvider::default(), incoming_rx)
1277 .with_blob_store(Box::new(blob_store));
1278 let (response, rx) = oneshot::channel();
1279 let request =
1280 GetCells { hashes: vec![B256::ZERO; MAX_CELLS_SERVE + 1], cell_mask: B128::default() };
1281
1282 handler.on_cells_request(PeerId::default(), request, response);
1283
1284 let cells = rx.await.unwrap().unwrap();
1285 assert!(cells.hashes.is_empty());
1286 assert_eq!(get_cells_calls.load(Ordering::Relaxed), MAX_CELLS_SERVE);
1287 }
1288
1289 #[test_case(0)]
1290 #[test_case(7)]
1291 #[test_case(8)]
1292 #[test_case(63)]
1293 #[test_case(64)]
1294 #[test_case(127)]
1295 #[tokio::test]
1296 async fn get_cells_request_uses_little_endian_wire_mask(index: u32) {
1297 let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1298 let numeric_mask = 1u128 << index;
1299 let blob_store = CountingBlobStore {
1300 expected_cell_mask: Some(BlobCellMask::from_bits(numeric_mask)),
1301 ..Default::default()
1302 };
1303 let handler = EthRequestHandler::<NoopProvider>::new(NoopProvider::default(), incoming_rx)
1304 .with_blob_store(Box::new(blob_store));
1305 let wire_mask = B128::from(numeric_mask.to_le_bytes());
1306 let request = GetCells { hashes: vec![B256::ZERO], cell_mask: wire_mask };
1307 let mut encoded = Vec::new();
1308 request.encode(&mut encoded);
1309 let request = GetCells::decode(&mut encoded.as_slice()).unwrap();
1310 let (response, rx) = oneshot::channel();
1311
1312 handler.on_cells_request(PeerId::default(), request, response);
1313
1314 let cells = rx.await.unwrap().unwrap();
1315 assert_eq!(cells.hashes, vec![B256::ZERO]);
1316 assert_eq!(cells.cells, vec![vec![Cell::default()]]);
1317 assert_eq!(cells.cell_mask, wire_mask);
1318 encoded.clear();
1319 cells.encode(&mut encoded);
1320 assert_eq!(Cells::decode(&mut encoded.as_slice()).unwrap(), cells);
1321 }
1322
1323 #[tokio::test]
1324 async fn get_node_data_responds_with_empty_payload() {
1325 let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1326 let handler = EthRequestHandler::<NoopProvider>::new(NoopProvider::default(), incoming_rx);
1327 let (response, rx) = oneshot::channel();
1328
1329 handler.on_node_data_request(PeerId::default(), GetNodeData(vec![B256::ZERO]), response);
1330
1331 let node_data = rx.await.expect("response channel must not be dropped").unwrap();
1332 assert!(node_data.0.is_empty(), "GetNodeData should reply with empty NodeData");
1333 }
1334
1335 fn snap_handler(
1337 provider: MockEthProvider,
1338 ) -> EthRequestHandler<MockEthProvider, EthNetworkPrimitives> {
1339 let (_incoming_tx, incoming_rx) = mpsc::channel(1);
1340 EthRequestHandler::new(provider, incoming_rx)
1341 }
1342
1343 #[test_case(
1344 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1345 request_id: 1,
1346 root_hash: B256::ZERO,
1347 starting_hash: B256::ZERO,
1348 limit_hash: B256::repeat_byte(0xff),
1349 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1350 }),
1351 SnapResponse::AccountRange(AccountRangeMessage {
1352 request_id: 1,
1353 accounts: Vec::new(),
1354 proof: Vec::new(),
1355 }); "account range"
1356 )]
1357 #[test_case(
1358 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1359 request_id: 2,
1360 root_hash: B256::ZERO,
1361 account_hashes: vec![B256::ZERO],
1362 starting_hash: B256::ZERO.into(),
1363 limit_hash: B256::repeat_byte(0xff).into(),
1364 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1365 }),
1366 SnapResponse::StorageRanges(StorageRangesMessage {
1367 request_id: 2,
1368 slots: Vec::new(),
1369 proof: Vec::new(),
1370 }); "storage ranges"
1371 )]
1372 #[test_case(
1373 SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1374 request_id: 3,
1375 hashes: vec![B256::repeat_byte(0x11)],
1376 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1377 }),
1378 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 3, codes: Vec::new() }); "bytecodes"
1379 )]
1380 #[tokio::test]
1381 async fn snap_requests_return_empty_responses_on_provider_errors(
1382 request: SnapProtocolMessage,
1383 expected: SnapResponse,
1384 ) {
1385 let provider = MockEthProvider::default();
1386 provider.set_snap_state_reads_fail(true);
1387 let mut handler = snap_handler(provider);
1388 let (response, rx) = oneshot::channel();
1389
1390 handler.on_snap_request(PeerId::default(), request, response);
1391
1392 assert_eq!(rx.await.unwrap(), Ok(expected));
1393 }
1394
1395 #[tokio::test]
1396 async fn unavailable_snap_state_returns_empty_response() {
1397 let provider = MockEthProvider::default();
1398 let mut handler = snap_handler(provider.clone());
1399 let (response, rx) = oneshot::channel();
1400 handler.on_snap_request(
1401 PeerId::default(),
1402 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1403 request_id: 1,
1404 root_hash: B256::ZERO,
1405 starting_hash: B256::ZERO,
1406 limit_hash: B256::repeat_byte(0xff),
1407 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1408 }),
1409 response,
1410 );
1411
1412 assert_eq!(
1413 rx.await.unwrap(),
1414 Ok(SnapResponse::AccountRange(AccountRangeMessage {
1415 request_id: 1,
1416 accounts: Vec::new(),
1417 proof: Vec::new(),
1418 }))
1419 );
1420 assert_eq!(provider.snap_state_range_resolutions(), 1);
1421 }
1422
1423 fn account_range_request() -> SnapProtocolMessage {
1424 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1425 request_id: 1,
1426 root_hash: B256::ZERO,
1427 starting_hash: B256::ZERO,
1428 limit_hash: B256::repeat_byte(0xff),
1429 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1430 })
1431 }
1432
1433 fn empty_account_range() -> SnapResponse {
1434 SnapResponse::AccountRange(AccountRangeMessage {
1435 request_id: 1,
1436 accounts: Vec::new(),
1437 proof: Vec::new(),
1438 })
1439 }
1440
1441 #[tokio::test]
1442 async fn busy_state_range_request_waits_for_a_slot() {
1443 let provider = MockEthProvider::default();
1444 let mut handler = snap_handler(provider.clone());
1445 let peer_id = PeerId::default();
1447 let (release, released) = oneshot::channel::<()>();
1448 handler.state_range_requests.tasks.push((
1449 peer_id,
1450 tokio::spawn(async move {
1451 let _ = released.await;
1452 }),
1453 ));
1454 let busy_peer = PeerId::repeat_byte(1);
1455 for peer in std::iter::once(busy_peer).chain((2..).map(PeerId::repeat_byte)) {
1456 if handler.state_range_requests.tasks.len() == MAX_CONCURRENT_STATE_RANGE_REQUESTS {
1457 break
1458 }
1459 handler
1460 .state_range_requests
1461 .tasks
1462 .push((peer, tokio::spawn(futures::future::pending())));
1463 }
1464 let (response, mut passed_over) = oneshot::channel();
1466 handler.on_snap_request(busy_peer, account_range_request(), response);
1467 let (response, mut rx) = oneshot::channel();
1468 handler.on_snap_request(peer_id, account_range_request(), response);
1469
1470 assert_eq!(handler.state_range_requests.queue.len(), 2);
1471 assert_eq!(provider.snap_state_range_resolutions(), 0);
1472
1473 release.send(()).unwrap();
1474 let EthRequestHandler { state_range_requests, client, metrics, .. } = &mut handler;
1477 let wait = Duration::from_secs(5);
1478 let started = Instant::now();
1479 let mut served = None;
1480 tokio::time::timeout(
1481 wait,
1482 std::future::poll_fn(|cx| {
1483 state_range_requests.poll(cx, client, metrics);
1484 if served.is_none() {
1485 served = rx.try_recv().ok();
1486 }
1487 if served.is_some() &&
1488 state_range_requests.tasks.len() < MAX_CONCURRENT_STATE_RANGE_REQUESTS
1489 {
1490 Poll::Ready(())
1491 } else {
1492 Poll::Pending
1493 }
1494 }),
1495 )
1496 .await
1497 .expect("queued state range request must be served once a slot frees");
1498
1499 assert!(started.elapsed() < wait, "task started from the queue must wake the poller");
1500 assert_eq!(served, Some(Ok(empty_account_range())));
1501 assert_eq!(provider.snap_state_range_resolutions(), 1);
1502 assert_eq!(handler.state_range_requests.queue.len(), 1);
1503 assert_eq!(passed_over.try_recv(), Err(oneshot::error::TryRecvError::Empty));
1504 }
1505
1506 #[tokio::test]
1507 async fn stale_queued_state_range_requests_are_not_served() {
1508 let provider = MockEthProvider::default();
1509 let mut handler = snap_handler(provider.clone());
1510 for byte in 0..MAX_CONCURRENT_STATE_RANGE_REQUESTS as u8 {
1511 handler
1512 .state_range_requests
1513 .tasks
1514 .push((PeerId::repeat_byte(byte), tokio::spawn(futures::future::pending())));
1515 }
1516 let (response, gone) = oneshot::channel();
1518 handler.on_snap_request(PeerId::repeat_byte(0x80), account_range_request(), response);
1519 drop(gone);
1520 let SnapProtocolMessage::GetAccountRange(request) = account_range_request() else {
1522 unreachable!("fixture is an account range request")
1523 };
1524 let (response, mut expired) = oneshot::channel();
1525 handler.state_range_requests.queue.push_back(IncomingStateRangeRequest {
1526 peer_id: PeerId::repeat_byte(0x81),
1527 request: StateRangeRequest::Account(request),
1528 response,
1529 received: Instant::now() + Duration::from_millis(50) -
1530 STATE_RANGE_REQUEST_QUEUE_TIMEOUT,
1531 });
1532
1533 let EthRequestHandler { state_range_requests, client, metrics, .. } = &mut handler;
1536 let wait = Duration::from_secs(5);
1537 let started = Instant::now();
1538 let answer = tokio::time::timeout(
1539 wait,
1540 std::future::poll_fn(|cx| {
1541 state_range_requests.poll(cx, client, metrics);
1542 Pin::new(&mut expired).poll(cx)
1543 }),
1544 )
1545 .await
1546 .expect("queued state range request must be answered when it times out");
1547
1548 assert!(started.elapsed() < wait, "queue timeout must wake the poller");
1549 assert_eq!(answer.unwrap(), Ok(empty_account_range()));
1550 assert!(handler.state_range_requests.queue.is_empty());
1551 assert_eq!(provider.snap_state_range_resolutions(), 0);
1552 }
1553
1554 #[tokio::test]
1555 async fn incoming_state_range_request_sees_slots_freed_since_the_last_poll() {
1556 let provider = MockEthProvider::default();
1557 let (incoming_tx, incoming_rx) = mpsc::channel(1);
1558 let mut handler = snap_handler(provider.clone());
1559 handler.incoming_requests = ReceiverStream::new(incoming_rx);
1560 let peer_id = PeerId::default();
1562 let finished = tokio::spawn(async {});
1563 while !finished.is_finished() {
1564 tokio::task::yield_now().await;
1565 }
1566 handler.state_range_requests.tasks.push((peer_id, finished));
1567 for byte in 1..MAX_CONCURRENT_STATE_RANGE_REQUESTS as u8 {
1568 handler
1569 .state_range_requests
1570 .tasks
1571 .push((PeerId::repeat_byte(byte), tokio::spawn(futures::future::pending())));
1572 }
1573 let (response, _queued) = oneshot::channel();
1574 handler.on_snap_request(peer_id, account_range_request(), response);
1575 assert_eq!(handler.state_range_requests.queue.len(), 1);
1576
1577 let (response, mut rx) = oneshot::channel();
1579 incoming_tx
1580 .send(IncomingEthRequest::GetSnap {
1581 peer_id,
1582 request: account_range_request(),
1583 response,
1584 })
1585 .await
1586 .unwrap();
1587 tokio::time::timeout(
1588 Duration::from_secs(5),
1589 std::future::poll_fn(|cx| {
1590 let _ = Pin::new(&mut handler).poll(cx);
1591 Pin::new(&mut rx).poll(cx)
1592 }),
1593 )
1594 .await
1595 .expect("incoming state range request must be answered")
1596 .unwrap()
1597 .unwrap();
1598
1599 assert_eq!(provider.snap_state_range_resolutions(), 2);
1601 }
1602
1603 #[tokio::test]
1604 async fn overflowing_state_range_requests_return_empty_responses() {
1605 let provider = MockEthProvider::default();
1606 let mut handler = snap_handler(provider.clone());
1607 for byte in 0..MAX_CONCURRENT_STATE_RANGE_REQUESTS as u8 {
1608 handler
1609 .state_range_requests
1610 .tasks
1611 .push((PeerId::repeat_byte(byte), tokio::spawn(futures::future::pending())));
1612 }
1613 let queued_peer = |index: usize| PeerId::repeat_byte(0x80 + index as u8);
1614 let (response, queued) = oneshot::channel();
1615 handler.on_snap_request(queued_peer(0), account_range_request(), response);
1616
1617 let (response, rx) = oneshot::channel();
1619 handler.on_snap_request(queued_peer(0), account_range_request(), response);
1620 assert_eq!(rx.await.unwrap(), Ok(empty_account_range()));
1621
1622 let mut waiting = vec![queued];
1624 for index in 1..MAX_QUEUED_STATE_RANGE_REQUESTS {
1625 let (response, rx) = oneshot::channel();
1626 handler.on_snap_request(queued_peer(index), account_range_request(), response);
1627 waiting.push(rx);
1628 }
1629 let (response, rx) = oneshot::channel();
1630 handler.on_snap_request(PeerId::repeat_byte(0xff), account_range_request(), response);
1631 assert_eq!(rx.await.unwrap(), Ok(empty_account_range()));
1632
1633 assert!(waiting
1635 .iter_mut()
1636 .all(|rx| rx.try_recv() == Err(oneshot::error::TryRecvError::Empty)));
1637 assert_eq!(provider.snap_state_range_resolutions(), 0);
1638 }
1639
1640 #[tokio::test]
1641 async fn state_range_request_from_gone_peer_is_dropped() {
1642 let mut handler = snap_handler(MockEthProvider::default());
1643 let (response, rx) = oneshot::channel();
1644 drop(rx);
1645
1646 handler.on_snap_request(PeerId::default(), account_range_request(), response);
1647
1648 assert!(handler.state_range_requests.tasks.is_empty());
1649 }
1650
1651 #[tokio::test]
1652 async fn finished_state_range_request_frees_its_peer_slot() {
1653 let mut handler = snap_handler(MockEthProvider::default());
1654 let (response, rx) = oneshot::channel();
1655
1656 handler.on_snap_request(PeerId::default(), account_range_request(), response);
1657
1658 assert_eq!(handler.state_range_requests.tasks.len(), 1);
1659 rx.await.unwrap().unwrap();
1660 tokio::time::timeout(
1661 Duration::from_secs(5),
1662 std::future::poll_fn(|cx| {
1663 let _ = Pin::new(&mut handler).poll(cx);
1664 if handler.state_range_requests.tasks.is_empty() {
1665 Poll::Ready(())
1666 } else {
1667 Poll::Pending
1668 }
1669 }),
1670 )
1671 .await
1672 .expect("finished state range task must free its peer slot");
1673 }
1674
1675 #[tokio::test]
1676 async fn snap_requests_return_empty_responses_on_inconsistent_provider_results() {
1677 let missing_storage_root = MockEthProvider::default();
1678 missing_storage_root
1679 .set_snap_account_range(vec![(B256::ZERO, Account::default())], RangeEnd::Exhausted);
1680
1681 let missing_account_proof = MockEthProvider::default();
1682 missing_account_proof.set_snap_account_range(Vec::new(), RangeEnd::Exhausted);
1683
1684 let missing_storage_proof = MockEthProvider::default();
1685 missing_storage_proof
1686 .push_snap_storage_range(vec![(B256::ZERO, U256::ONE)], RangeEnd::ByteLimit);
1687
1688 let storage_disappears = MockEthProvider::default();
1689 storage_disappears.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1690 storage_disappears.push_unavailable_snap_storage_range();
1691
1692 let account_request = || {
1693 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1694 request_id: 1,
1695 root_hash: B256::ZERO,
1696 starting_hash: B256::ZERO,
1697 limit_hash: B256::repeat_byte(0xff),
1698 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1699 })
1700 };
1701 let storage_request = |account_hashes| {
1702 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1703 request_id: 2,
1704 root_hash: B256::ZERO,
1705 account_hashes,
1706 starting_hash: B256::ZERO.into(),
1707 limit_hash: B256::repeat_byte(0xff).into(),
1708 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1709 })
1710 };
1711 let empty_accounts = SnapResponse::AccountRange(AccountRangeMessage {
1712 request_id: 1,
1713 accounts: Vec::new(),
1714 proof: Vec::new(),
1715 });
1716 let empty_storage = SnapResponse::StorageRanges(StorageRangesMessage {
1717 request_id: 2,
1718 slots: Vec::new(),
1719 proof: Vec::new(),
1720 });
1721 let cases = [
1722 (missing_storage_root, account_request(), empty_accounts.clone()),
1723 (missing_account_proof, account_request(), empty_accounts),
1724 (missing_storage_proof, storage_request(vec![B256::ZERO]), empty_storage.clone()),
1725 (storage_disappears, storage_request(vec![B256::ZERO, B256::ZERO]), empty_storage),
1726 ];
1727
1728 for (provider, request, expected) in cases {
1729 let mut handler = snap_handler(provider.clone());
1730 let (response, rx) = oneshot::channel();
1731 handler.on_snap_request(PeerId::default(), request, response);
1732 assert_eq!(rx.await.unwrap(), Ok(expected));
1733 assert_eq!(provider.snap_state_range_resolutions(), 1);
1734 }
1735 }
1736
1737 #[tokio::test]
1738 #[allow(clippy::needless_update)]
1739 async fn snap_account_range_response_encodes_accounts_and_proof() {
1740 let provider = MockEthProvider::default();
1741 let first_hash = B256::repeat_byte(0x01);
1742 let second_hash = B256::repeat_byte(0x02);
1743 let storage_root = B256::repeat_byte(0x11);
1744 let code_hash = B256::repeat_byte(0x22);
1745 let proof = vec![Bytes::from_static(&[0xaa])];
1746 provider.set_snap_account_range(
1747 vec![
1748 (
1749 first_hash,
1750 Account {
1751 nonce: 1,
1752 balance: U256::from(2),
1753 bytecode_hash: Some(code_hash),
1754 ..Default::default()
1755 },
1756 ),
1757 (second_hash, Account { nonce: 3, balance: U256::from(4), ..Default::default() }),
1758 ],
1759 RangeEnd::HashLimit,
1762 );
1763 provider.set_snap_storage_root(first_hash, storage_root);
1764 provider.set_snap_storage_root(second_hash, EMPTY_ROOT_HASH);
1765 provider.set_snap_account_proof(Some(proof.clone()));
1766
1767 let mut full_body = vec![0xf8, 0x44, 0x01, 0x02, 0xa0];
1768 full_body.extend_from_slice(storage_root.as_slice());
1769 full_body.push(0xa0);
1770 full_body.extend_from_slice(code_hash.as_slice());
1771 let empty_body = Bytes::from_static(&[0xc4, 0x03, 0x04, 0x80, 0x80]);
1772
1773 let mut handler = snap_handler(provider.clone());
1774 let (response, rx) = oneshot::channel();
1775 handler.on_snap_request(
1776 PeerId::default(),
1777 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1778 request_id: 1,
1779 root_hash: B256::ZERO,
1780 starting_hash: B256::ZERO,
1781 limit_hash: B256::repeat_byte(0xff),
1782 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1783 }),
1784 response,
1785 );
1786
1787 assert_eq!(
1788 rx.await.unwrap(),
1789 Ok(SnapResponse::AccountRange(AccountRangeMessage {
1790 request_id: 1,
1791 accounts: vec![
1792 AccountData {
1793 hash: first_hash,
1794 body: alloy_rlp::decode_exact(&full_body).unwrap()
1795 },
1796 AccountData {
1797 hash: second_hash,
1798 body: alloy_rlp::decode_exact(&empty_body).unwrap()
1799 },
1800 ],
1801 proof,
1802 }))
1803 );
1804 assert_eq!(provider.snap_state_range_resolutions(), 1);
1805 }
1806
1807 #[tokio::test]
1808 async fn snap_account_range_response_skips_proof_for_exhausted_zero_origin_range() {
1809 let provider = MockEthProvider::default();
1810 let hash = B256::repeat_byte(0x01);
1811 provider.set_snap_account_range(
1812 vec![(hash, Account { nonce: 1, balance: U256::from(2), ..Default::default() })],
1813 RangeEnd::Exhausted,
1814 );
1815 provider.set_snap_storage_root(hash, EMPTY_ROOT_HASH);
1816 provider.set_snap_account_proof(Some(vec![Bytes::from_static(&[0xaa])]));
1819
1820 let mut handler = snap_handler(provider.clone());
1821 let (response, rx) = oneshot::channel();
1822 handler.on_snap_request(
1823 PeerId::default(),
1824 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
1825 request_id: 1,
1826 root_hash: B256::ZERO,
1827 starting_hash: B256::ZERO,
1828 limit_hash: B256::repeat_byte(0xff),
1829 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1830 }),
1831 response,
1832 );
1833
1834 let Ok(SnapResponse::AccountRange(AccountRangeMessage { accounts, proof, .. })) =
1835 rx.await.unwrap()
1836 else {
1837 panic!("expected an account range response");
1838 };
1839 assert_eq!(accounts.len(), 1);
1840 assert!(proof.is_empty(), "a zero-origin, exhausted range must not carry a boundary proof");
1841 }
1842
1843 #[tokio::test]
1844 async fn snap_storage_range_response_encodes_values_and_proof() {
1845 let provider = MockEthProvider::default();
1846 let first_hash = B256::repeat_byte(0x01);
1847 let second_hash = B256::repeat_byte(0x02);
1848 let origin = B256::repeat_byte(0x10);
1849 let proof = vec![Bytes::from_static(&[0xbb])];
1850 provider.push_snap_storage_range(
1851 vec![(first_hash, U256::from(0x0102)), (second_hash, U256::from(0xff))],
1852 RangeEnd::Exhausted,
1853 );
1854 provider.set_snap_storage_proof(Some(proof.clone()));
1855
1856 let mut handler = snap_handler(provider.clone());
1857 let (response, rx) = oneshot::channel();
1858 handler.on_snap_request(
1859 PeerId::default(),
1860 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1861 request_id: 2,
1862 root_hash: B256::ZERO,
1863 account_hashes: vec![B256::repeat_byte(0x03)],
1864 starting_hash: origin.into(),
1865 limit_hash: B256::repeat_byte(0xff).into(),
1866 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1867 }),
1868 response,
1869 );
1870
1871 assert_eq!(
1872 rx.await.unwrap(),
1873 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1874 request_id: 2,
1875 slots: vec![vec![
1876 StorageData { hash: first_hash, data: Bytes::from_static(&[0x82, 0x01, 0x02]) },
1877 StorageData { hash: second_hash, data: Bytes::from_static(&[0x81, 0xff]) },
1878 ]],
1879 proof,
1880 }))
1881 );
1882 assert_eq!(provider.snap_storage_range_requests()[0].1, origin);
1883 assert_eq!(provider.snap_state_range_resolutions(), 1);
1884 }
1885
1886 #[tokio::test]
1887 async fn snap_storage_ranges_only_bound_the_first_account() {
1888 let provider = MockEthProvider::default();
1889 provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1890 provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1891 let first_account = B256::repeat_byte(0x01);
1892 let second_account = B256::repeat_byte(0x02);
1893 let origin = B256::ZERO;
1894 let limit = B256::repeat_byte(0x22);
1895
1896 let mut handler = snap_handler(provider.clone());
1897 let (response, rx) = oneshot::channel();
1898 handler.on_snap_request(
1899 PeerId::default(),
1900 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1901 request_id: 3,
1902 root_hash: B256::ZERO,
1903 account_hashes: vec![first_account, second_account],
1904 starting_hash: origin.into(),
1905 limit_hash: limit.into(),
1906 response_bytes: 1_000,
1907 }),
1908 response,
1909 );
1910
1911 assert_eq!(
1912 rx.await.unwrap(),
1913 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1914 request_id: 3,
1915 slots: vec![Vec::new(), Vec::new()],
1916 proof: Vec::new(),
1917 }))
1918 );
1919 assert_eq!(
1920 provider.snap_storage_range_requests(),
1921 vec![
1922 (first_account, origin, limit, 1_000),
1923 (second_account, B256::ZERO, B256::repeat_byte(0xff), 1_000),
1924 ]
1925 );
1926 assert_eq!(provider.snap_state_range_resolutions(), 1);
1927 }
1928
1929 #[tokio::test]
1930 async fn snap_byte_codes_response_preserves_found_code_order() {
1931 let provider = MockEthProvider::default();
1932 let code = Bytes::from_static(&[0x60, 0x00]);
1933 let code_hash = keccak256(&code);
1934 let later_code = Bytes::from_static(&[0x60, 0x01]);
1935 let later_code_hash = keccak256(&later_code);
1936 provider.add_account(
1937 Address::repeat_byte(0x01),
1938 ExtendedAccount::new(1, U256::ZERO).with_bytecode(code.clone()),
1939 );
1940 provider.add_account(
1941 Address::repeat_byte(0x02),
1942 ExtendedAccount::new(1, U256::ZERO).with_bytecode(later_code),
1943 );
1944
1945 let mut handler = snap_handler(provider);
1946 let (response, rx) = oneshot::channel();
1947 handler.on_snap_request(
1948 PeerId::default(),
1949 SnapProtocolMessage::GetByteCodes(reth_eth_wire::snap::GetByteCodesMessage {
1950 request_id: 3,
1951 hashes: vec![KECCAK_EMPTY, B256::repeat_byte(0xff), code_hash, later_code_hash],
1952 response_bytes: 1,
1953 }),
1954 response,
1955 );
1956
1957 assert_eq!(
1958 rx.await.unwrap(),
1959 Ok(SnapResponse::ByteCodes(ByteCodesMessage {
1960 request_id: 3,
1961 codes: vec![Bytes::new(), code],
1962 }))
1963 );
1964 }
1965
1966 #[tokio::test]
1967 async fn snap_storage_ranges_limit_account_lookups() {
1968 let provider = MockEthProvider::default();
1969 for _ in 0..=MAX_STORAGE_RANGE_ACCOUNTS_SERVE {
1970 provider.push_snap_storage_range(Vec::new(), RangeEnd::Exhausted);
1971 }
1972 let mut handler = snap_handler(provider.clone());
1973 let (response, rx) = oneshot::channel();
1974 handler.on_snap_request(
1975 PeerId::default(),
1976 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
1977 request_id: 4,
1978 root_hash: B256::ZERO,
1979 account_hashes: vec![B256::ZERO; MAX_STORAGE_RANGE_ACCOUNTS_SERVE + 1],
1980 starting_hash: B256::ZERO.into(),
1981 limit_hash: B256::repeat_byte(0xff).into(),
1982 response_bytes: SOFT_RESPONSE_LIMIT as u64,
1983 }),
1984 response,
1985 );
1986
1987 assert_eq!(
1988 rx.await.unwrap(),
1989 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
1990 request_id: 4,
1991 slots: vec![Vec::new(); MAX_STORAGE_RANGE_ACCOUNTS_SERVE],
1992 proof: Vec::new(),
1993 }))
1994 );
1995 assert_eq!(provider.snap_storage_ranges_remaining(), 1);
1996 assert_eq!(provider.snap_state_range_resolutions(), 1);
1997 }
1998
1999 #[tokio::test]
2000 async fn snap_storage_range_proves_finite_limit_from_zero_origin() {
2001 let provider = MockEthProvider::default();
2002 let hash = B256::repeat_byte(0x01);
2003 let proof = vec![Bytes::from_static(&[0xcc])];
2004 provider.push_snap_storage_range(vec![(hash, U256::ONE)], RangeEnd::HashLimit);
2007 provider.set_snap_storage_proof(Some(proof.clone()));
2008
2009 let mut handler = snap_handler(provider.clone());
2010 let (response, rx) = oneshot::channel();
2011 handler.on_snap_request(
2012 PeerId::default(),
2013 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
2014 request_id: 5,
2015 root_hash: B256::ZERO,
2016 account_hashes: vec![B256::repeat_byte(0x03)],
2017 starting_hash: B256::ZERO.into(),
2018 limit_hash: B256::repeat_byte(0x20).into(),
2019 response_bytes: SOFT_RESPONSE_LIMIT as u64,
2020 }),
2021 response,
2022 );
2023
2024 assert_eq!(
2025 rx.await.unwrap(),
2026 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
2027 request_id: 5,
2028 slots: vec![vec![StorageData { hash, data: Bytes::from_static(&[0x01]) }]],
2029 proof,
2030 }))
2031 );
2032 }
2033
2034 #[tokio::test]
2035 async fn snap_storage_ranges_are_entirely_empty_when_an_account_is_missing() {
2036 let provider = MockEthProvider::default();
2037 provider.push_missing_snap_storage_account();
2038 provider.push_snap_storage_range(
2039 vec![(B256::repeat_byte(0x01), U256::ONE)],
2040 RangeEnd::Exhausted,
2041 );
2042 let missing_account = B256::repeat_byte(0x01);
2043 let valid_account = B256::repeat_byte(0x02);
2044
2045 let mut handler = snap_handler(provider.clone());
2046 let (response, rx) = oneshot::channel();
2047 handler.on_snap_request(
2048 PeerId::default(),
2049 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
2050 request_id: 6,
2051 root_hash: B256::ZERO,
2052 account_hashes: vec![missing_account, valid_account],
2053 starting_hash: B256::ZERO.into(),
2054 limit_hash: B256::repeat_byte(0xff).into(),
2055 response_bytes: SOFT_RESPONSE_LIMIT as u64,
2056 }),
2057 response,
2058 );
2059
2060 assert_eq!(
2061 rx.await.unwrap(),
2062 Ok(SnapResponse::StorageRanges(StorageRangesMessage {
2063 request_id: 6,
2064 slots: Vec::new(),
2065 proof: Vec::new(),
2066 }))
2067 );
2068 assert_eq!(provider.snap_storage_ranges_remaining(), 1);
2071 }
2072
2073 fn receipt_with_data(data_len: usize) -> Receipt {
2075 Receipt {
2076 tx_type: TxType::Legacy,
2077 success: true,
2078 cumulative_gas_used: 21_000,
2079 logs: vec![Log {
2080 address: Address::ZERO,
2081 data: LogData::new_unchecked(Vec::new(), vec![0u8; data_len].into()),
2082 }],
2083 }
2084 }
2085
2086 fn insert_receipts_block(
2088 provider: &MockEthProvider,
2089 number: u64,
2090 receipts: Vec<Receipt>,
2091 ) -> B256 {
2092 let hash = B256::with_last_byte(number as u8);
2093 provider.add_header(hash, Header { number, ..Default::default() });
2094 provider.add_receipts(number, receipts);
2095 hash
2096 }
2097
2098 async fn get_receipts70(
2100 provider: MockEthProvider,
2101 first_block_receipt_index: u64,
2102 block_hashes: Vec<B256>,
2103 ) -> Receipts70 {
2104 let handler = snap_handler(provider);
2105 let (response, rx) = oneshot::channel();
2106 handler.on_receipts70_request(
2107 PeerId::default(),
2108 GetReceipts70 { first_block_receipt_index, block_hashes },
2109 response,
2110 );
2111 rx.await.unwrap().unwrap()
2112 }
2113
2114 #[tokio::test]
2115 async fn receipts70_serves_block_over_soft_limit_complete() {
2116 let provider = MockEthProvider::default();
2117 let small = receipt_with_data(16);
2119 let large = receipt_with_data(SOFT_RESPONSE_LIMIT);
2120 let hash = insert_receipts_block(&provider, 1, vec![small.clone(), large.clone()]);
2121
2122 let resp = get_receipts70(provider, 0, vec![hash]).await;
2123
2124 assert_eq!(
2125 resp,
2126 Receipts70 { last_block_incomplete: false, receipts: vec![vec![small, large]] }
2127 );
2128 }
2129
2130 #[tokio::test]
2131 async fn receipts70_serves_last_block_over_soft_limit_complete() {
2132 let provider = MockEthProvider::default();
2133 let first = vec![receipt_with_data(1024); 160];
2134 let first_hash = insert_receipts_block(&provider, 1, first.clone());
2135 let second = vec![receipt_with_data(SOFT_RESPONSE_LIMIT)];
2138 let second_hash = insert_receipts_block(&provider, 2, second.clone());
2139
2140 let resp = get_receipts70(provider, 0, vec![first_hash, second_hash]).await;
2141
2142 assert_eq!(
2143 resp,
2144 Receipts70 { last_block_incomplete: false, receipts: vec![first, second] }
2145 );
2146 }
2147
2148 #[tokio::test]
2149 async fn receipts70_omits_block_whose_first_receipt_overflows_the_message() {
2150 let provider = MockEthProvider::default();
2151 let first = vec![receipt_with_data(SOFT_RESPONSE_LIMIT - 1024)];
2152 let first_hash = insert_receipts_block(&provider, 1, first.clone());
2153 let second_hash =
2156 insert_receipts_block(&provider, 2, vec![receipt_with_data(9 * 1024 * 1024)]);
2157
2158 let resp = get_receipts70(provider, 0, vec![first_hash, second_hash]).await;
2159
2160 assert_eq!(resp, Receipts70 { last_block_incomplete: false, receipts: vec![first] });
2161 }
2162
2163 #[tokio::test]
2164 async fn receipts70_serves_partial_block_over_message_limit() {
2165 let provider = MockEthProvider::default();
2166 let receipts = vec![receipt_with_data(1024 * 1024); 12];
2169 let hash = insert_receipts_block(&provider, 1, receipts.clone());
2170
2171 let resp = get_receipts70(provider, 0, vec![hash]).await;
2172
2173 assert!(resp.last_block_incomplete);
2174 assert_eq!(resp.receipts.len(), 1);
2175 let partial = &resp.receipts[0];
2176 assert!(!partial.is_empty() && partial.len() < receipts.len());
2177 assert_eq!(partial.as_slice(), &receipts[..partial.len()]);
2178
2179 let encoded = encode_receipts70_response(resp);
2180 assert!(encoded.len() > SOFT_RESPONSE_LIMIT);
2182 assert!(encoded.len() <= MAX_MESSAGE_SIZE);
2183 }
2184
2185 #[test_case(0, 0; "first receipt at hard limit")]
2186 #[test_case(1, 0; "continuation receipt at hard limit")]
2187 #[test_case(0, 1; "first receipt framing exceeds hard limit")]
2188 #[test_case(1, 1; "continuation receipt framing exceeds hard limit")]
2189 #[tokio::test]
2190 async fn receipts70_enforces_framed_response_limit(first_receipt_index: u64, excess: usize) {
2191 let data_len = MAX_MESSAGE_SIZE - 1024;
2194 let encoded = encode_receipts70_response(Receipts70 {
2195 last_block_incomplete: false,
2196 receipts: vec![vec![receipt_with_data(data_len)]],
2197 });
2198 let receipt = receipt_with_data(data_len + MAX_MESSAGE_SIZE - encoded.len() + excess);
2199 assert!(receipt.length() < MAX_MESSAGE_SIZE);
2200 let expected =
2201 Receipts70 { last_block_incomplete: false, receipts: vec![vec![receipt.clone()]] };
2202 assert_eq!(encode_receipts70_response(expected.clone()).len(), MAX_MESSAGE_SIZE + excess);
2203
2204 let provider = MockEthProvider::default();
2205 let mut receipts = vec![receipt_with_data(16); first_receipt_index as usize];
2206 receipts.push(receipt);
2207 let hash = insert_receipts_block(&provider, 1, receipts);
2208
2209 let resp = get_receipts70(provider, first_receipt_index, vec![hash]).await;
2210
2211 if excess == 0 {
2212 assert_eq!(resp, expected);
2213 } else {
2214 assert_eq!(resp, Receipts70 { last_block_incomplete: false, receipts: Vec::new() });
2215 }
2216 let encoded = encode_receipts70_response(resp);
2217 assert!(encoded.len() <= MAX_MESSAGE_SIZE);
2218 let mut requester = EthStreamInner::<EthNetworkPrimitives>::new(EthVersion::Eth70);
2220 requester
2221 .encode_message(EthMessage::GetReceipts70(RequestPair {
2222 request_id: u64::MAX,
2223 message: GetReceipts70 {
2224 first_block_receipt_index: first_receipt_index,
2225 block_hashes: vec![hash],
2226 },
2227 }))
2228 .unwrap();
2229 requester.decode_message(encoded.as_ref().into()).unwrap();
2230 }
2231
2232 fn encode_receipts70_response(response: Receipts70) -> Bytes {
2234 EthStreamInner::<EthNetworkPrimitives>::new(EthVersion::Eth70)
2235 .encode_message(EthMessage::Receipts70(RequestPair {
2236 request_id: u64::MAX,
2237 message: response,
2238 }))
2239 .unwrap()
2240 .into()
2241 }
2242}