1use alloy_consensus::BlockHeader;
4use alloy_eips::BlockNumberOrTag;
5use alloy_primitives::{Sealable, TxHash};
6use alloy_rpc_types_eth::{
7 error::EthRpcErrorCode, Filter, FilterBlockOption, FilterChanges, FilterId,
8 PendingTransactionFilterKind,
9};
10use async_trait::async_trait;
11use futures::{
12 future::TryFutureExt,
13 stream::{FuturesOrdered, StreamExt},
14 Future,
15};
16use itertools::Itertools;
17use jsonrpsee::{core::RpcResult, server::IdProvider};
18use reth_errors::ProviderError;
19use reth_primitives_traits::{NodePrimitives, SealedHeader};
20use reth_rpc_eth_api::{
21 helpers::{EthBlocks, LoadReceipt},
22 EngineEthFilter, EthApiTypes, EthFilterApiServer, FullEthApiTypes, QueryLimits, RpcConvert,
23 RpcLog, RpcNodeCoreExt, RpcTransaction,
24};
25use reth_rpc_eth_types::{
26 logs_utils::{self, append_matching_block_logs, ProviderOrBlock},
27 EthApiError, EthFilterConfig, EthStateCache, EthSubscriptionIdProvider,
28};
29use reth_rpc_server_types::{result::rpc_error_with_code, ToRpcResult};
30use reth_storage_api::{
31 BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, HeaderProvider, ProviderBlock,
32 ProviderReceipt, ReceiptProvider,
33};
34use reth_tasks::Runtime;
35use reth_transaction_pool::{NewSubpoolTransactionStream, PoolTransaction, TransactionPool};
36use std::{
37 collections::{HashMap, VecDeque},
38 fmt,
39 iter::{Peekable, StepBy},
40 ops::RangeInclusive,
41 pin::Pin,
42 sync::Arc,
43 time::{Duration, Instant},
44};
45use tokio::{
46 sync::{mpsc::Receiver, oneshot, Mutex},
47 time::MissedTickBehavior,
48};
49use tracing::{debug, error, trace};
50
51impl<Eth> EngineEthFilter<RpcLog<Eth::NetworkTypes>> for EthFilter<Eth>
52where
53 Eth: FullEthApiTypes + RpcNodeCoreExt<Provider: BlockIdReader> + LoadReceipt + EthBlocks,
54{
55 fn logs(
57 &self,
58 filter: Filter,
59 limits: QueryLimits,
60 ) -> impl Future<Output = RpcResult<Vec<RpcLog<Eth::NetworkTypes>>>> + Send {
61 trace!(target: "rpc::eth", "Serving eth_getLogs");
62 self.logs_for_filter(filter, limits).map_err(|e| e.into())
63 }
64}
65
66const CACHED_MODE_BLOCK_THRESHOLD: u64 = 250;
68
69const HIGH_BLOOM_MATCH_THRESHOLD: usize = 20;
71
72const MODERATE_BLOOM_MATCH_THRESHOLD: usize = 10;
74
75const BLOOM_ADJUSTMENT_MIN_BLOCKS: u64 = 100;
77
78const MAX_HEADERS_RANGE: u64 = 1_000; const _: () = assert!(CACHED_MODE_BLOCK_THRESHOLD <= MAX_HEADERS_RANGE);
84
85const PARALLEL_PROCESSING_THRESHOLD: usize = 1000;
88
89const DEFAULT_PARALLEL_CONCURRENCY: usize = 4;
91
92const MAX_PARALLEL_BATCH_SIZE: usize = 256;
94
95pub struct EthFilter<Eth: EthApiTypes> {
99 inner: Arc<EthFilterInner<Eth>>,
101}
102
103impl<Eth> Clone for EthFilter<Eth>
104where
105 Eth: EthApiTypes,
106{
107 fn clone(&self) -> Self {
108 Self { inner: self.inner.clone() }
109 }
110}
111
112impl<Eth> EthFilter<Eth>
113where
114 Eth: EthApiTypes + 'static,
115{
116 pub fn new(eth_api: Eth, config: EthFilterConfig, task_spawner: Runtime) -> Self {
144 let EthFilterConfig { max_blocks_per_filter, max_logs_per_response, stale_filter_ttl } =
145 config;
146 let inner = EthFilterInner {
147 eth_api,
148 active_filters: ActiveFilters::new(),
149 id_provider: Arc::new(EthSubscriptionIdProvider::default()),
150 max_headers_range: MAX_HEADERS_RANGE,
151 task_spawner,
152 stale_filter_ttl,
153 query_limits: QueryLimits { max_blocks_per_filter, max_logs_per_response },
154 };
155
156 let eth_filter = Self { inner: Arc::new(inner) };
157
158 let this = eth_filter.clone();
159 eth_filter.inner.task_spawner.spawn_critical_task(
160 "eth-filters_stale-filters-clean",
161 async move {
162 this.watch_and_clear_stale_filters().await;
163 },
164 );
165
166 eth_filter
167 }
168
169 pub fn active_filters(&self) -> &ActiveFilters<RpcTransaction<Eth::NetworkTypes>> {
171 &self.inner.active_filters
172 }
173
174 async fn watch_and_clear_stale_filters(&self) {
177 let mut interval = tokio::time::interval_at(
178 tokio::time::Instant::now() + self.inner.stale_filter_ttl,
179 self.inner.stale_filter_ttl,
180 );
181 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
182 loop {
183 interval.tick().await;
184 self.clear_stale_filters(Instant::now()).await;
185 }
186 }
187
188 pub async fn clear_stale_filters(&self, now: Instant) {
191 trace!(target: "rpc::eth", "clear stale filters");
192 let mut filters = self.active_filters().inner.lock().await;
193 filters.retain(|id, filter| {
194 let is_valid = (now - filter.last_poll_timestamp) < self.inner.stale_filter_ttl;
195
196 if !is_valid {
197 trace!(target: "rpc::eth", "evict filter with id: {:?}", id);
198 }
199
200 is_valid
201 });
202 filters.shrink_to_fit();
203 }
204}
205
206impl<Eth> EthFilter<Eth>
207where
208 Eth: FullEthApiTypes<Provider: BlockReader + BlockIdReader> + LoadReceipt + EthBlocks,
209{
210 fn provider(&self) -> &Eth::Provider {
212 self.inner.eth_api.provider()
213 }
214
215 fn pool(&self) -> &Eth::Pool {
217 self.inner.eth_api.pool()
218 }
219
220 pub async fn filter_changes(
222 &self,
223 id: FilterId,
224 ) -> Result<
225 FilterChanges<RpcTransaction<Eth::NetworkTypes>, RpcLog<Eth::NetworkTypes>>,
226 EthFilterError,
227 > {
228 let info = self.provider().chain_info()?;
229 let best_number = info.best_number;
230
231 let (start_block, kind) = {
234 let mut filters = self.inner.active_filters.inner.lock().await;
235 let filter = filters.get_mut(&id).ok_or(EthFilterError::FilterNotFound(id))?;
236
237 if filter.block > best_number {
238 return Ok(FilterChanges::Empty)
240 }
241
242 let mut block = best_number + 1;
246 std::mem::swap(&mut filter.block, &mut block);
247 filter.last_poll_timestamp = Instant::now();
248
249 (block, filter.kind.clone())
250 };
251
252 match kind {
253 FilterKind::PendingTransaction(filter) => Ok(match filter.drain().await {
254 FilterChanges::Empty => FilterChanges::Empty,
255 FilterChanges::Hashes(hashes) => FilterChanges::Hashes(hashes),
256 FilterChanges::Transactions(transactions) => {
257 FilterChanges::Transactions(transactions)
258 }
259 FilterChanges::Logs(_) => unreachable!("pending transaction filter returned logs"),
260 }),
261 FilterKind::Block => {
262 let end_block = best_number + 1;
265 let block_hashes =
266 self.provider().canonical_hashes_range(start_block, end_block).map_err(
267 |_| EthApiError::HeaderRangeNotFound(start_block.into(), end_block.into()),
268 )?;
269 Ok(FilterChanges::Hashes(block_hashes))
270 }
271 FilterKind::Log(filter) => {
272 let (from_block_number, to_block_number) = match filter.block_option {
273 FilterBlockOption::Range { from_block, to_block } => {
274 let from = from_block
275 .map(|num| self.provider().convert_block_number(num))
276 .transpose()?
277 .flatten();
278 let to = to_block
279 .map(|num| self.provider().convert_block_number(num))
280 .transpose()?
281 .flatten();
282 logs_utils::get_filter_block_range(from, to, start_block, info)?
283 }
284 FilterBlockOption::AtBlockHash(block_hash) => {
285 let block_number = self
289 .provider()
290 .block_number(block_hash)?
291 .ok_or(ProviderError::HeaderNotFound(block_hash.into()))?;
292 (block_number, block_number)
293 }
294 };
295 let logs = self
296 .inner
297 .clone()
298 .get_logs_in_block_range(
299 *filter,
300 from_block_number,
301 to_block_number,
302 self.inner.query_limits,
303 )
304 .await?;
305 Ok(FilterChanges::Logs(logs))
306 }
307 }
308 }
309
310 pub async fn filter_logs(
316 &self,
317 id: FilterId,
318 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
319 let filter = {
320 let mut filters = self.inner.active_filters.inner.lock().await;
321 let filter =
322 filters.get_mut(&id).ok_or_else(|| EthFilterError::FilterNotFound(id.clone()))?;
323 if let FilterKind::Log(ref inner_filter) = filter.kind {
324 filter.last_poll_timestamp = Instant::now();
325 *inner_filter.clone()
326 } else {
327 return Err(EthFilterError::FilterNotFound(id))
329 }
330 };
331
332 self.logs_for_filter(filter, self.inner.query_limits).await
333 }
334
335 async fn logs_for_filter(
337 &self,
338 filter: Filter,
339 limits: QueryLimits,
340 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
341 self.inner.clone().logs_for_filter(filter, limits).await
342 }
343}
344
345#[async_trait]
346impl<Eth> EthFilterApiServer<RpcTransaction<Eth::NetworkTypes>, RpcLog<Eth::NetworkTypes>>
347 for EthFilter<Eth>
348where
349 Eth: FullEthApiTypes + LoadReceipt + EthBlocks,
350{
351 async fn new_filter(&self, filter: Filter) -> RpcResult<FilterId> {
353 trace!(target: "rpc::eth", "Serving eth_newFilter");
354 self.inner
355 .install_filter(FilterKind::<RpcTransaction<Eth::NetworkTypes>>::Log(Box::new(filter)))
356 .await
357 }
358
359 async fn new_block_filter(&self) -> RpcResult<FilterId> {
361 trace!(target: "rpc::eth", "Serving eth_newBlockFilter");
362 self.inner.install_filter(FilterKind::<RpcTransaction<Eth::NetworkTypes>>::Block).await
363 }
364
365 async fn new_pending_transaction_filter(
367 &self,
368 kind: Option<PendingTransactionFilterKind>,
369 ) -> RpcResult<FilterId> {
370 trace!(target: "rpc::eth", "Serving eth_newPendingTransactionFilter");
371
372 let transaction_kind = match kind.unwrap_or_default() {
373 PendingTransactionFilterKind::Hashes => {
374 let receiver = self.pool().pending_transactions_listener();
375 let pending_txs_receiver = PendingTransactionsReceiver::new(receiver);
376 FilterKind::PendingTransaction(PendingTransactionKind::Hashes(pending_txs_receiver))
377 }
378 PendingTransactionFilterKind::Full => {
379 let stream = self.pool().new_pending_pool_transactions_listener();
380 let full_txs_receiver = FullTransactionsReceiver::new(
381 stream,
382 dyn_clone::clone(self.inner.eth_api.converter()),
383 );
384 FilterKind::PendingTransaction(PendingTransactionKind::FullTransaction(Arc::new(
385 full_txs_receiver,
386 )))
387 }
388 };
389
390 self.inner.install_filter(transaction_kind).await
392 }
393
394 async fn filter_changes(
396 &self,
397 id: FilterId,
398 ) -> RpcResult<FilterChanges<RpcTransaction<Eth::NetworkTypes>, RpcLog<Eth::NetworkTypes>>>
399 {
400 trace!(target: "rpc::eth", "Serving eth_getFilterChanges");
401 Ok(Self::filter_changes(self, id).await?)
402 }
403
404 async fn filter_logs(&self, id: FilterId) -> RpcResult<Vec<RpcLog<Eth::NetworkTypes>>> {
410 trace!(target: "rpc::eth", "Serving eth_getFilterLogs");
411 Ok(Self::filter_logs(self, id).await?)
412 }
413
414 async fn uninstall_filter(&self, id: FilterId) -> RpcResult<bool> {
416 trace!(target: "rpc::eth", "Serving eth_uninstallFilter");
417 let mut filters = self.inner.active_filters.inner.lock().await;
418 if filters.remove(&id).is_some() {
419 trace!(target: "rpc::eth::filter", ?id, "uninstalled filter");
420 Ok(true)
421 } else {
422 Ok(false)
423 }
424 }
425
426 async fn logs(&self, filter: Filter) -> RpcResult<Vec<RpcLog<Eth::NetworkTypes>>> {
430 trace!(target: "rpc::eth", "Serving eth_getLogs");
431 Ok(self.logs_for_filter(filter, self.inner.query_limits).await?)
432 }
433}
434
435impl<Eth> std::fmt::Debug for EthFilter<Eth>
436where
437 Eth: EthApiTypes,
438{
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 f.debug_struct("EthFilter").finish_non_exhaustive()
441 }
442}
443
444#[derive(Debug)]
446struct EthFilterInner<Eth: EthApiTypes> {
447 eth_api: Eth,
449 active_filters: ActiveFilters<RpcTransaction<Eth::NetworkTypes>>,
451 id_provider: Arc<dyn IdProvider>,
453 query_limits: QueryLimits,
455 max_headers_range: u64,
457 task_spawner: Runtime,
459 stale_filter_ttl: Duration,
461}
462
463impl<Eth> EthFilterInner<Eth>
464where
465 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool>
466 + EthApiTypes<NetworkTypes: reth_rpc_eth_api::types::RpcTypes>
467 + LoadReceipt
468 + EthBlocks,
469{
470 fn provider(&self) -> &Eth::Provider {
472 self.eth_api.provider()
473 }
474
475 fn eth_cache(&self) -> &EthStateCache<Eth::Primitives> {
477 self.eth_api.cache()
478 }
479
480 async fn logs_for_filter(
482 self: Arc<Self>,
483 filter: Filter,
484 limits: QueryLimits,
485 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
486 match filter.block_option {
487 FilterBlockOption::AtBlockHash(block_hash) => {
488 let Some((receipts, maybe_block)) =
490 self.eth_cache().get_receipts_and_maybe_block(block_hash).await?
491 else {
492 return Err(match self.provider().block_number(block_hash)? {
494 Some(number) => {
495 let earliest_available = self.provider().earliest_block_number()?;
496 if number < earliest_available {
497 EthApiError::PrunedHistoryUnavailable {
498 requested: number,
499 earliest_available,
500 }
501 .into()
502 } else {
503 EthFilterError::ReceiptsUnavailable(number)
504 }
505 }
506 None => ProviderError::HeaderNotFound(block_hash.into()).into(),
507 })
508 };
509
510 let header = if let Some(block) = &maybe_block {
511 block.clone_sealed_header()
512 } else {
513 let header = self
514 .provider()
515 .header_by_hash_or_number(block_hash.into())?
516 .ok_or_else(|| ProviderError::HeaderNotFound(block_hash.into()))?;
517 SealedHeader::new(header, block_hash)
518 };
519
520 let earliest_block = self.provider().earliest_block_number()?;
522 if header.number() < earliest_block {
523 return Err(EthApiError::PrunedHistoryUnavailable {
524 requested: header.number(),
525 earliest_available: earliest_block,
526 }
527 .into());
528 }
529
530 if !filter.matches_bloom(header.logs_bloom()) {
531 return Ok(Vec::new())
532 }
533
534 let mut all_logs = Vec::new();
535 append_matching_block_logs(
536 &mut all_logs,
537 self.eth_api.converter(),
538 maybe_block
539 .map(ProviderOrBlock::Block)
540 .unwrap_or_else(|| ProviderOrBlock::Provider(self.provider())),
541 &filter,
542 &header,
543 &receipts,
544 false,
545 )?;
546 Ok(all_logs)
547 }
548 FilterBlockOption::Range { from_block, to_block } => {
549 if from_block.is_some_and(|b| b.is_pending()) {
551 let to_block = to_block.unwrap_or(BlockNumberOrTag::Pending);
552 if !(to_block.is_pending() || to_block.is_number()) {
553 return Ok(Vec::new());
555 }
556 if let Ok(Some(pending_block)) = self.eth_api.local_pending_block().await {
558 if let BlockNumberOrTag::Number(to_block) = to_block &&
559 to_block < pending_block.block.number()
560 {
561 return Ok(Vec::new());
563 }
564
565 let info = self.provider().chain_info()?;
566 if pending_block.block.number() > info.best_number {
567 let mut all_logs = Vec::new();
569 let header = pending_block.block.clone_sealed_header();
570 append_matching_block_logs(
571 &mut all_logs,
572 self.eth_api.converter(),
573 ProviderOrBlock::<Eth::Provider>::Block(pending_block.block),
574 &filter,
575 &header,
576 &pending_block.receipts,
577 false, )?;
579 return Ok(all_logs)
580 }
581 }
582 }
583
584 let info = self.provider().chain_info()?;
585 let start_block = info.best_number;
586 let from = from_block
589 .filter(|num| !num.is_pending())
590 .map(|num| self.provider().convert_block_number(num))
591 .transpose()?
592 .flatten();
593 let to = to_block
594 .filter(|num| !num.is_pending())
595 .map(|num| self.provider().convert_block_number(num))
596 .transpose()?
597 .flatten();
598
599 if let Some(t) = to &&
601 t > info.best_number
602 {
603 return Err(EthFilterError::BlockRangeExceedsHead {
604 requested: t,
605 head: info.best_number,
606 });
607 }
608
609 let (from_block_number, to_block_number) =
610 logs_utils::get_filter_block_range(from, to, start_block, info)?;
611
612 let earliest_block = self.provider().earliest_block_number()?;
614 if from_block_number < earliest_block {
615 return Err(EthApiError::PrunedHistoryUnavailable {
616 requested: from_block_number,
617 earliest_available: earliest_block,
618 }
619 .into());
620 }
621
622 self.get_logs_in_block_range(filter, from_block_number, to_block_number, limits)
623 .await
624 }
625 }
626 }
627
628 async fn install_filter(
630 &self,
631 kind: FilterKind<RpcTransaction<Eth::NetworkTypes>>,
632 ) -> RpcResult<FilterId> {
633 let last_poll_block_number = self.provider().best_block_number().to_rpc_result()?;
634 let subscription_id = self.id_provider.next_id();
635
636 let id = match subscription_id {
637 jsonrpsee_types::SubscriptionId::Num(n) => FilterId::Num(n),
638 jsonrpsee_types::SubscriptionId::Str(s) => FilterId::Str(s.into_owned()),
639 };
640 let mut filters = self.active_filters.inner.lock().await;
641 filters.insert(
642 id.clone(),
643 ActiveFilter {
644 block: last_poll_block_number,
645 last_poll_timestamp: Instant::now(),
646 kind,
647 },
648 );
649 Ok(id)
650 }
651
652 async fn get_logs_in_block_range(
658 self: Arc<Self>,
659 filter: Filter,
660 from_block: u64,
661 to_block: u64,
662 limits: QueryLimits,
663 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
664 trace!(target: "rpc::eth::filter", from=from_block, to=to_block, ?filter, "finding logs in range");
665
666 if to_block < from_block {
668 return Err(EthFilterError::InvalidBlockRangeParams)
669 }
670
671 if let Some(max_blocks_per_filter) =
672 limits.max_blocks_per_filter.filter(|limit| to_block - from_block > *limit)
673 {
674 return Err(EthFilterError::QueryExceedsMaxBlocks(max_blocks_per_filter))
675 }
676
677 let permit = self
681 .eth_api
682 .acquire_owned_blocking_io()
683 .await
684 .map_err(|_| EthFilterError::InternalError)?;
685
686 let (mut tx, rx) = oneshot::channel();
687 let this = self.clone();
688 self.task_spawner.spawn_blocking_task(async move {
689 let _permit = permit;
690 let fut = this.get_logs_in_block_range_inner(&filter, from_block, to_block, limits);
691 tokio::pin!(fut);
692 let res = tokio::select! {
693 biased;
695 _ = tx.closed() => None,
696 res = &mut fut => Some(res),
697 };
698 if let Some(res) = res {
699 let _ = tx.send(res);
700 }
701 });
702
703 rx.await.map_err(|_| EthFilterError::InternalError)?
704 }
705
706 async fn get_logs_in_block_range_inner(
715 self: Arc<Self>,
716 filter: &Filter,
717 from_block: u64,
718 to_block: u64,
719 limits: QueryLimits,
720 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
721 let mut all_logs = Vec::new();
722
723 let chain_tip = self.provider().best_block_number()?;
725
726 for (from, to) in
730 BlockRangeInclusiveIter::new(from_block..=to_block, self.max_headers_range)
731 {
732 tokio::task::yield_now().await;
734
735 let mut matching_headers = Vec::new();
737 let headers = self.provider().headers_range(from..=to)?;
738
739 let mut headers_iter = headers.into_iter().peekable();
740
741 while let Some(header) = headers_iter.next() {
742 if !filter.matches_bloom(header.logs_bloom()) {
743 continue
744 }
745
746 let current_number = header.number();
747
748 let block_hash = match headers_iter.peek() {
749 Some(next_header) if next_header.number() == current_number + 1 => {
750 next_header.parent_hash()
752 }
753 _ => {
754 header.hash_slow()
756 }
757 };
758
759 matching_headers.push(SealedHeader::new(header, block_hash));
760 }
761
762 let mut range_mode = RangeMode::new(
764 self.clone(),
765 matching_headers,
766 from_block,
767 to_block,
768 self.max_headers_range,
769 chain_tip,
770 );
771
772 while let Some(ReceiptBlockResult { receipts, recovered_block, header }) =
774 range_mode.next().await?
775 {
776 let num_hash = header.num_hash();
777 append_matching_block_logs(
778 &mut all_logs,
779 self.eth_api.converter(),
780 recovered_block
781 .map(ProviderOrBlock::Block)
782 .unwrap_or_else(|| ProviderOrBlock::Provider(self.provider())),
783 filter,
784 &header,
785 &receipts,
786 false,
787 )?;
788
789 let is_multi_block_range = from_block != to_block;
792 if let Some(max_logs_per_response) = limits.max_logs_per_response &&
793 is_multi_block_range &&
794 all_logs.len() > max_logs_per_response
795 {
796 let retry_to_block = if num_hash.number == from_block {
797 from_block
798 } else {
799 num_hash.number - 1
800 };
801
802 debug!(
803 target: "rpc::eth::filter",
804 logs_found = all_logs.len(),
805 max_logs_per_response,
806 from_block,
807 to_block = retry_to_block,
808 "Query exceeded max logs per response limit"
809 );
810 return Err(EthFilterError::QueryExceedsMaxResults {
811 max_logs: max_logs_per_response,
812 from_block,
813 to_block: retry_to_block,
814 });
815 }
816 }
817 }
818
819 Ok(all_logs)
820 }
821}
822
823#[derive(Debug, Clone, Default)]
825pub struct ActiveFilters<T> {
826 inner: Arc<Mutex<HashMap<FilterId, ActiveFilter<T>>>>,
827}
828
829impl<T> ActiveFilters<T> {
830 pub fn new() -> Self {
832 Self { inner: Arc::new(Mutex::new(HashMap::default())) }
833 }
834
835 pub async fn contains(&self, id: &FilterId) -> bool {
837 self.inner.lock().await.contains_key(id)
838 }
839
840 pub async fn len(&self) -> usize {
842 self.inner.lock().await.len()
843 }
844
845 pub async fn is_empty(&self) -> bool {
847 self.inner.lock().await.is_empty()
848 }
849
850 pub async fn ids(&self) -> Vec<FilterId> {
852 self.inner.lock().await.keys().cloned().collect()
853 }
854}
855
856#[derive(Debug)]
858struct ActiveFilter<T> {
859 block: u64,
861 last_poll_timestamp: Instant,
863 kind: FilterKind<T>,
865}
866
867#[derive(Debug, Clone)]
869struct PendingTransactionsReceiver {
870 txs_receiver: Arc<Mutex<Receiver<TxHash>>>,
871}
872
873impl PendingTransactionsReceiver {
874 fn new(receiver: Receiver<TxHash>) -> Self {
875 Self { txs_receiver: Arc::new(Mutex::new(receiver)) }
876 }
877
878 async fn drain<T>(&self) -> FilterChanges<T> {
880 let mut pending_txs = Vec::new();
881 let mut prepared_stream = self.txs_receiver.lock().await;
882
883 while let Ok(tx_hash) = prepared_stream.try_recv() {
884 pending_txs.push(tx_hash);
885 }
886
887 FilterChanges::Hashes(pending_txs)
889 }
890}
891
892#[derive(Debug, Clone)]
894struct FullTransactionsReceiver<T: PoolTransaction, TxCompat> {
895 txs_stream: Arc<Mutex<NewSubpoolTransactionStream<T>>>,
896 converter: TxCompat,
897}
898
899impl<T, TxCompat> FullTransactionsReceiver<T, TxCompat>
900where
901 T: PoolTransaction + 'static,
902 TxCompat: RpcConvert<Primitives: NodePrimitives<SignedTx = T::Consensus>>,
903{
904 fn new(stream: NewSubpoolTransactionStream<T>, converter: TxCompat) -> Self {
906 Self { txs_stream: Arc::new(Mutex::new(stream)), converter }
907 }
908
909 async fn drain(&self) -> FilterChanges<RpcTransaction<TxCompat::Network>> {
911 let mut pending_txs = Vec::new();
912 let mut prepared_stream = self.txs_stream.lock().await;
913
914 while let Ok(tx) = prepared_stream.try_recv() {
915 match self.converter.fill_pending(tx.transaction.to_consensus()) {
916 Ok(tx) => pending_txs.push(tx),
917 Err(err) => {
918 error!(target: "rpc",
919 %err,
920 "Failed to fill txn with block context"
921 );
922 }
923 }
924 }
925 FilterChanges::Transactions(pending_txs)
926 }
927}
928
929#[async_trait]
931trait FullTransactionsFilter<T>: fmt::Debug + Send + Sync + Unpin + 'static {
932 async fn drain(&self) -> FilterChanges<T>;
933}
934
935#[async_trait]
936impl<T, TxCompat> FullTransactionsFilter<RpcTransaction<TxCompat::Network>>
937 for FullTransactionsReceiver<T, TxCompat>
938where
939 T: PoolTransaction + 'static,
940 TxCompat: RpcConvert<Primitives: NodePrimitives<SignedTx = T::Consensus>>,
941{
942 async fn drain(&self) -> FilterChanges<RpcTransaction<TxCompat::Network>> {
943 Self::drain(self).await
944 }
945}
946
947#[derive(Debug, Clone)]
953enum PendingTransactionKind<T> {
954 Hashes(PendingTransactionsReceiver),
955 FullTransaction(Arc<dyn FullTransactionsFilter<T>>),
956}
957
958impl<T: 'static> PendingTransactionKind<T> {
959 async fn drain(&self) -> FilterChanges<T> {
960 match self {
961 Self::Hashes(receiver) => receiver.drain().await,
962 Self::FullTransaction(receiver) => receiver.drain().await,
963 }
964 }
965}
966
967#[derive(Clone, Debug)]
968enum FilterKind<T> {
969 Log(Box<Filter>),
970 Block,
971 PendingTransaction(PendingTransactionKind<T>),
972}
973
974#[derive(Debug)]
976struct BlockRangeInclusiveIter {
977 iter: StepBy<RangeInclusive<u64>>,
978 step: u64,
979 end: u64,
980}
981
982impl BlockRangeInclusiveIter {
983 fn new(range: RangeInclusive<u64>, step: u64) -> Self {
984 Self { end: *range.end(), iter: range.step_by(step as usize + 1), step }
985 }
986}
987
988impl Iterator for BlockRangeInclusiveIter {
989 type Item = (u64, u64);
990
991 fn next(&mut self) -> Option<Self::Item> {
992 let start = self.iter.next()?;
993 let end = (start + self.step).min(self.end);
994 if start > end {
995 return None
996 }
997 Some((start, end))
998 }
999}
1000
1001#[derive(Debug, thiserror::Error)]
1003pub enum EthFilterError {
1004 #[error("filter not found")]
1006 FilterNotFound(FilterId),
1007 #[error("invalid block range params")]
1009 InvalidBlockRangeParams,
1010 #[error("block range extends beyond current head block: requested {requested}, head {head}")]
1012 BlockRangeExceedsHead {
1013 requested: u64,
1015 head: u64,
1017 },
1018 #[error("query exceeds max block range {0}")]
1020 QueryExceedsMaxBlocks(u64),
1021 #[error("pruned history unavailable")]
1023 ReceiptsUnavailable(u64),
1024 #[error("query exceeds max results {max_logs}, retry with the range {from_block}-{to_block}")]
1026 QueryExceedsMaxResults {
1027 max_logs: usize,
1029 from_block: u64,
1031 to_block: u64,
1033 },
1034 #[error(transparent)]
1036 EthAPIError(#[from] EthApiError),
1037 #[error("internal filter error")]
1039 InternalError,
1040}
1041
1042impl From<EthFilterError> for jsonrpsee::types::error::ErrorObject<'static> {
1043 fn from(err: EthFilterError) -> Self {
1044 match err {
1045 EthFilterError::FilterNotFound(_) => rpc_error_with_code(
1047 jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE,
1048 "filter not found",
1049 ),
1050 err @ EthFilterError::InternalError => {
1051 rpc_error_with_code(jsonrpsee::types::error::INTERNAL_ERROR_CODE, err.to_string())
1052 }
1053 EthFilterError::EthAPIError(err) => err.into(),
1054 err @ EthFilterError::ReceiptsUnavailable(_) => {
1055 rpc_error_with_code(EthRpcErrorCode::PrunedHistory.code(), err.to_string())
1056 }
1057 err @ (EthFilterError::InvalidBlockRangeParams |
1058 EthFilterError::QueryExceedsMaxBlocks(_) |
1059 EthFilterError::QueryExceedsMaxResults { .. } |
1060 EthFilterError::BlockRangeExceedsHead { .. }) => {
1061 rpc_error_with_code(jsonrpsee::types::error::INVALID_PARAMS_CODE, err.to_string())
1062 }
1063 }
1064 }
1065}
1066
1067impl From<ProviderError> for EthFilterError {
1068 fn from(err: ProviderError) -> Self {
1069 Self::EthAPIError(err.into())
1070 }
1071}
1072
1073impl From<logs_utils::FilterBlockRangeError> for EthFilterError {
1074 fn from(err: logs_utils::FilterBlockRangeError) -> Self {
1075 match err {
1076 logs_utils::FilterBlockRangeError::InvalidBlockRange => Self::InvalidBlockRangeParams,
1077 logs_utils::FilterBlockRangeError::BlockRangeExceedsHead { requested, head } => {
1078 Self::BlockRangeExceedsHead { requested, head }
1079 }
1080 }
1081 }
1082}
1083
1084struct ReceiptBlockResult<P>
1087where
1088 P: BlockReader,
1089{
1090 receipts: Arc<Vec<ProviderReceipt<P>>>,
1092 recovered_block: Option<Arc<reth_primitives_traits::RecoveredBlock<ProviderBlock<P>>>>,
1094 header: SealedHeader<<P as HeaderProvider>::Header>,
1096}
1097
1098enum RangeMode<
1100 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1101> {
1102 Cached(CachedMode<Eth>),
1104 Range(RangeBlockMode<Eth>),
1106}
1107
1108impl<
1109 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1110 > RangeMode<Eth>
1111{
1112 fn new(
1114 filter_inner: Arc<EthFilterInner<Eth>>,
1115 sealed_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1116 from_block: u64,
1117 to_block: u64,
1118 max_headers_range: u64,
1119 chain_tip: u64,
1120 ) -> Self {
1121 let block_count = to_block - from_block + 1;
1122 let distance_from_tip = chain_tip.saturating_sub(to_block);
1123
1124 let use_cached_mode =
1126 Self::should_use_cached_mode(&sealed_headers, block_count, distance_from_tip);
1127
1128 let parallel = sealed_headers.len() >= PARALLEL_PROCESSING_THRESHOLD;
1131
1132 if use_cached_mode && !sealed_headers.is_empty() {
1133 Self::Cached(CachedMode { filter_inner, headers_iter: sealed_headers.into_iter() })
1134 } else {
1135 Self::Range(RangeBlockMode {
1136 filter_inner,
1137 iter: sealed_headers.into_iter().peekable(),
1138 next: VecDeque::new(),
1139 max_range: (max_headers_range as usize).min(MAX_PARALLEL_BATCH_SIZE),
1140 parallel,
1141 pending_tasks: FuturesOrdered::new(),
1142 })
1143 }
1144 }
1145
1146 const fn should_use_cached_mode(
1148 headers: &[SealedHeader<<Eth::Provider as HeaderProvider>::Header>],
1149 block_count: u64,
1150 distance_from_tip: u64,
1151 ) -> bool {
1152 let bloom_matches = headers.len();
1154
1155 let adjusted_threshold = Self::calculate_adjusted_threshold(block_count, bloom_matches);
1157
1158 block_count <= adjusted_threshold && distance_from_tip <= adjusted_threshold
1159 }
1160
1161 const fn calculate_adjusted_threshold(block_count: u64, bloom_matches: usize) -> u64 {
1163 if block_count <= BLOOM_ADJUSTMENT_MIN_BLOCKS {
1165 return CACHED_MODE_BLOCK_THRESHOLD;
1166 }
1167
1168 match bloom_matches {
1169 n if n > HIGH_BLOOM_MATCH_THRESHOLD => CACHED_MODE_BLOCK_THRESHOLD / 2,
1170 n if n > MODERATE_BLOOM_MATCH_THRESHOLD => (CACHED_MODE_BLOCK_THRESHOLD * 3) / 4,
1171 _ => CACHED_MODE_BLOCK_THRESHOLD,
1172 }
1173 }
1174
1175 async fn next(&mut self) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1177 match self {
1178 Self::Cached(cached) => cached.next().await,
1179 Self::Range(range) => range.next().await,
1180 }
1181 }
1182}
1183
1184struct CachedMode<
1186 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1187> {
1188 filter_inner: Arc<EthFilterInner<Eth>>,
1189 headers_iter: std::vec::IntoIter<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1190}
1191
1192impl<
1193 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1194 > CachedMode<Eth>
1195{
1196 async fn next(&mut self) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1197 let Some(header) = self.headers_iter.next() else { return Ok(None) };
1198
1199 let Some((receipts, maybe_block)) =
1201 self.filter_inner.eth_cache().get_receipts_and_maybe_block(header.hash()).await?
1202 else {
1203 return Err(EthFilterError::ReceiptsUnavailable(header.number()))
1204 };
1205
1206 Ok(Some(ReceiptBlockResult { receipts, recovered_block: maybe_block, header }))
1207 }
1208}
1209
1210type ReceiptFetchFuture<P> =
1212 Pin<Box<dyn Future<Output = Result<Vec<ReceiptBlockResult<P>>, EthFilterError>> + Send>>;
1213
1214struct RangeBlockMode<
1216 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1217> {
1218 filter_inner: Arc<EthFilterInner<Eth>>,
1219 iter: Peekable<std::vec::IntoIter<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>>,
1220 next: VecDeque<ReceiptBlockResult<Eth::Provider>>,
1221 max_range: usize,
1223 parallel: bool,
1225 pending_tasks: FuturesOrdered<ReceiptFetchFuture<Eth::Provider>>,
1227}
1228
1229impl<
1230 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1231 > RangeBlockMode<Eth>
1232{
1233 async fn next(&mut self) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1234 loop {
1235 if let Some(result) = self.next.pop_front() {
1237 return Ok(Some(result));
1238 }
1239
1240 if let Some(task_result) = self.pending_tasks.next().await {
1242 self.next.extend(task_result?);
1243 continue;
1244 }
1245
1246 let Some(next_header) = self.iter.next() else {
1248 return Ok(None);
1250 };
1251
1252 if !self.parallel {
1253 if let Some(result) = self.process_small_range(vec![next_header]).await? {
1255 return Ok(Some(result));
1256 }
1257 continue;
1259 }
1260
1261 let mut range_headers = Vec::with_capacity(self.max_range.min(self.iter.len() + 1));
1262 range_headers.push(next_header);
1263
1264 while range_headers.len() < self.max_range {
1266 let Some(peeked) = self.iter.peek() else { break };
1267 let Some(last_header) = range_headers.last() else { break };
1268
1269 let expected_next = last_header.number() + 1;
1270 if peeked.number() != expected_next {
1271 trace!(
1272 target: "rpc::eth::filter",
1273 last_block = last_header.number(),
1274 next_block = peeked.number(),
1275 expected = expected_next,
1276 range_size = range_headers.len(),
1277 "Non-consecutive block detected, stopping range collection"
1278 );
1279 break; }
1281
1282 let Some(next_header) = self.iter.next() else { break };
1283 range_headers.push(next_header);
1284 }
1285
1286 self.spawn_parallel_tasks(range_headers);
1287 }
1289 }
1290
1291 async fn process_small_range(
1295 &mut self,
1296 range_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1297 ) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1298 for header in range_headers {
1300 let (maybe_block, maybe_receipts) = self
1302 .filter_inner
1303 .eth_cache()
1304 .maybe_cached_block_and_receipts(header.hash())
1305 .await?;
1306
1307 let receipts = match maybe_receipts {
1308 Some(receipts) => receipts,
1309 None => {
1310 match self.filter_inner.provider().receipts_by_block(header.hash().into())? {
1312 Some(receipts) => Arc::new(receipts),
1313 None => return Err(EthFilterError::ReceiptsUnavailable(header.number())),
1314 }
1315 }
1316 };
1317
1318 if !receipts.is_empty() {
1319 self.next.push_back(ReceiptBlockResult {
1320 receipts,
1321 recovered_block: maybe_block,
1322 header,
1323 });
1324 }
1325 }
1326
1327 Ok(self.next.pop_front())
1328 }
1329
1330 fn spawn_parallel_tasks(
1334 &mut self,
1335 range_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1336 ) {
1337 let chunk_size = range_headers.len().div_ceil(DEFAULT_PARALLEL_CONCURRENCY).max(1);
1339 let header_chunks = range_headers
1340 .into_iter()
1341 .chunks(chunk_size)
1342 .into_iter()
1343 .map(|chunk| chunk.collect::<Vec<_>>())
1344 .collect::<Vec<_>>();
1345
1346 for chunk_headers in header_chunks {
1348 let filter_inner = self.filter_inner.clone();
1349 let fetch = move || Self::fetch_chunk_receipts(&filter_inner, chunk_headers);
1350
1351 let chunk_task: ReceiptFetchFuture<Eth::Provider> = match self
1354 .filter_inner
1355 .eth_api
1356 .blocking_io_task_guard()
1357 .clone()
1358 .try_acquire_owned()
1359 {
1360 Ok(permit) => Box::pin(async move {
1361 let chunk_task = tokio::task::spawn_blocking(move || {
1362 let _permit = permit;
1363 fetch()
1364 });
1365
1366 match chunk_task.await {
1368 Ok(chunk_results) => chunk_results,
1369 Err(join_err) => {
1370 trace!(target: "rpc::eth::filter", error = ?join_err, "Task join error");
1371 Err(EthFilterError::InternalError)
1372 }
1373 }
1374 }),
1375 Err(_) => Box::pin(async move { fetch() }),
1376 };
1377
1378 self.pending_tasks.push_back(chunk_task);
1379 }
1380 }
1381
1382 fn fetch_chunk_receipts(
1384 filter_inner: &EthFilterInner<Eth>,
1385 chunk_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1386 ) -> Result<Vec<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1387 let mut chunk_results = Vec::with_capacity(chunk_headers.len());
1388
1389 for header in chunk_headers {
1390 let receipts = match filter_inner.provider().receipts_by_block(header.hash().into())? {
1393 Some(receipts) => Arc::new(receipts),
1394 None => return Err(EthFilterError::ReceiptsUnavailable(header.number())),
1395 };
1396
1397 if !receipts.is_empty() {
1398 chunk_results.push(ReceiptBlockResult { receipts, recovered_block: None, header });
1399 }
1400 }
1401
1402 Ok(chunk_results)
1403 }
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use super::*;
1409 use crate::{eth::EthApi, EthApiBuilder};
1410 use alloy_network::Ethereum;
1411 use alloy_primitives::{Bloom, FixedBytes};
1412 use rand::Rng;
1413 use reth_chainspec::{ChainSpec, ChainSpecProvider};
1414 use reth_ethereum_primitives::TxType;
1415 use reth_evm_ethereum::EthEvmConfig;
1416 use reth_network_api::noop::NoopNetwork;
1417 use reth_provider::test_utils::MockEthProvider;
1418 use reth_rpc_convert::RpcConverter;
1419 use reth_rpc_eth_api::node::RpcNodeCoreAdapter;
1420 use reth_rpc_eth_types::receipt::EthReceiptConverter;
1421 use reth_tasks::Runtime;
1422 use reth_testing_utils::generators;
1423 use reth_transaction_pool::test_utils::{testing_pool, TestPool};
1424 use std::{collections::VecDeque, sync::Arc};
1425
1426 #[test]
1427 fn receipts_unavailable_error_matches_geth() {
1428 let err: jsonrpsee::types::error::ErrorObject<'static> =
1429 EthFilterError::ReceiptsUnavailable(100).into();
1430 assert_eq!(err.code(), 4444);
1431 assert_eq!(err.message(), "pruned history unavailable");
1432 }
1433
1434 #[test]
1435 fn test_block_range_iter() {
1436 let mut rng = generators::rng();
1437
1438 let start = rng.random::<u32>() as u64;
1439 let end = start.saturating_add(rng.random::<u32>() as u64);
1440 let step = rng.random::<u16>() as u64;
1441 let range = start..=end;
1442 let mut iter = BlockRangeInclusiveIter::new(range.clone(), step);
1443 let (from, mut end) = iter.next().unwrap();
1444 assert_eq!(from, start);
1445 assert_eq!(end, (from + step).min(*range.end()));
1446
1447 for (next_from, next_end) in iter {
1448 assert_eq!(next_from, end + 1);
1450 end = next_end;
1451 }
1452
1453 assert_eq!(end, *range.end());
1454 }
1455
1456 #[expect(clippy::type_complexity)]
1458 fn build_test_eth_api(
1459 provider: MockEthProvider,
1460 ) -> EthApi<
1461 RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
1462 RpcConverter<Ethereum, EthEvmConfig, EthReceiptConverter<ChainSpec>>,
1463 > {
1464 EthApiBuilder::new(
1465 provider.clone(),
1466 testing_pool(),
1467 NoopNetwork::default(),
1468 EthEvmConfig::new(provider.chain_spec()),
1469 )
1470 .build()
1471 }
1472
1473 #[tokio::test]
1474 async fn test_logs_for_filter_from_block_beyond_head() {
1475 let provider = MockEthProvider::default();
1476 provider.add_header(FixedBytes::random(), alloy_consensus::Header::default());
1477 let eth_api = build_test_eth_api(provider);
1478
1479 let eth_filter =
1480 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1481
1482 let filter = Filter::new().from_block(100u64).to_block(BlockNumberOrTag::Latest);
1483 let result = eth_filter.inner.clone().logs_for_filter(filter, QueryLimits::default()).await;
1484 assert!(matches!(result, Err(EthFilterError::InvalidBlockRangeParams)), "{result:?}");
1485 }
1486
1487 #[tokio::test]
1488 async fn test_range_block_mode_empty_range() {
1489 let provider = MockEthProvider::default();
1490 let eth_api = build_test_eth_api(provider);
1491
1492 let eth_filter =
1493 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1494 let filter_inner = eth_filter.inner;
1495
1496 let headers = vec![];
1497 let max_range = 100;
1498
1499 let mut range_mode = RangeBlockMode {
1500 filter_inner,
1501 iter: headers.into_iter().peekable(),
1502 next: VecDeque::new(),
1503 max_range,
1504 parallel: false,
1505
1506 pending_tasks: FuturesOrdered::new(),
1507 };
1508
1509 let result = range_mode.next().await;
1510 assert!(result.is_ok());
1511 assert!(result.unwrap().is_none());
1512 }
1513
1514 #[tokio::test]
1515 async fn test_range_block_mode_queued_results_priority() {
1516 let provider = MockEthProvider::default();
1517
1518 let headers = vec![
1519 SealedHeader::new(
1520 alloy_consensus::Header { number: 100, ..Default::default() },
1521 FixedBytes::random(),
1522 ),
1523 SealedHeader::new(
1524 alloy_consensus::Header { number: 101, ..Default::default() },
1525 FixedBytes::random(),
1526 ),
1527 ];
1528 for header in &headers {
1529 provider.add_header(header.hash(), header.header().clone());
1530 provider.add_receipts(header.number(), vec![]);
1531 }
1532
1533 let eth_api = build_test_eth_api(provider);
1534
1535 let eth_filter =
1536 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1537 let filter_inner = eth_filter.inner;
1538
1539 let expected_block_hash_1 = FixedBytes::from([1u8; 32]);
1541 let expected_block_hash_2 = FixedBytes::from([2u8; 32]);
1542
1543 let mock_receipt_1 = reth_ethereum_primitives::Receipt {
1545 tx_type: TxType::Legacy,
1546 cumulative_gas_used: 100_000,
1547 logs: vec![],
1548 success: true,
1549 };
1550 let mock_receipt_2 = reth_ethereum_primitives::Receipt {
1551 tx_type: TxType::Eip1559,
1552 cumulative_gas_used: 200_000,
1553 logs: vec![],
1554 success: true,
1555 };
1556 let mock_receipt_3 = reth_ethereum_primitives::Receipt {
1557 tx_type: TxType::Eip2930,
1558 cumulative_gas_used: 150_000,
1559 logs: vec![],
1560 success: false, };
1562
1563 let mock_result_1 = ReceiptBlockResult {
1564 receipts: Arc::new(vec![mock_receipt_1.clone(), mock_receipt_2.clone()]),
1565 recovered_block: None,
1566 header: SealedHeader::new(
1567 alloy_consensus::Header { number: 42, ..Default::default() },
1568 expected_block_hash_1,
1569 ),
1570 };
1571
1572 let mock_result_2 = ReceiptBlockResult {
1573 receipts: Arc::new(vec![mock_receipt_3.clone()]),
1574 recovered_block: None,
1575 header: SealedHeader::new(
1576 alloy_consensus::Header { number: 43, ..Default::default() },
1577 expected_block_hash_2,
1578 ),
1579 };
1580
1581 let mut range_mode = RangeBlockMode {
1582 filter_inner,
1583 iter: headers.into_iter().peekable(),
1584 next: VecDeque::from([mock_result_1, mock_result_2]), max_range: 100,
1586 parallel: false,
1587
1588 pending_tasks: FuturesOrdered::new(),
1589 };
1590
1591 let result1 = range_mode.next().await;
1593 assert!(result1.is_ok());
1594 let receipt_result1 = result1.unwrap().unwrap();
1595 assert_eq!(receipt_result1.header.hash(), expected_block_hash_1);
1596 assert_eq!(receipt_result1.header.number, 42);
1597
1598 assert_eq!(receipt_result1.receipts.len(), 2);
1600 assert_eq!(receipt_result1.receipts[0].tx_type, mock_receipt_1.tx_type);
1601 assert_eq!(
1602 receipt_result1.receipts[0].cumulative_gas_used,
1603 mock_receipt_1.cumulative_gas_used
1604 );
1605 assert_eq!(receipt_result1.receipts[0].success, mock_receipt_1.success);
1606 assert_eq!(receipt_result1.receipts[1].tx_type, mock_receipt_2.tx_type);
1607 assert_eq!(
1608 receipt_result1.receipts[1].cumulative_gas_used,
1609 mock_receipt_2.cumulative_gas_used
1610 );
1611 assert_eq!(receipt_result1.receipts[1].success, mock_receipt_2.success);
1612
1613 let result2 = range_mode.next().await;
1615 assert!(result2.is_ok());
1616 let receipt_result2 = result2.unwrap().unwrap();
1617 assert_eq!(receipt_result2.header.hash(), expected_block_hash_2);
1618 assert_eq!(receipt_result2.header.number, 43);
1619
1620 assert_eq!(receipt_result2.receipts.len(), 1);
1622 assert_eq!(receipt_result2.receipts[0].tx_type, mock_receipt_3.tx_type);
1623 assert_eq!(
1624 receipt_result2.receipts[0].cumulative_gas_used,
1625 mock_receipt_3.cumulative_gas_used
1626 );
1627 assert_eq!(receipt_result2.receipts[0].success, mock_receipt_3.success);
1628
1629 assert!(range_mode.next.is_empty());
1631
1632 let result3 = range_mode.next().await;
1633 assert!(result3.is_ok());
1634 }
1635
1636 #[tokio::test]
1637 async fn test_range_block_mode_single_block_missing_receipts() {
1638 let provider = MockEthProvider::default();
1639 let eth_api = build_test_eth_api(provider);
1640
1641 let eth_filter =
1642 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1643 let filter_inner = eth_filter.inner;
1644
1645 let headers = vec![SealedHeader::new(
1646 alloy_consensus::Header { number: 100, ..Default::default() },
1647 FixedBytes::random(),
1648 )];
1649
1650 let mut range_mode = RangeBlockMode {
1651 filter_inner,
1652 iter: headers.into_iter().peekable(),
1653 next: VecDeque::new(),
1654 max_range: 100,
1655 parallel: false,
1656
1657 pending_tasks: FuturesOrdered::new(),
1658 };
1659
1660 let Err(err) = range_mode.next().await else { panic!("missing receipts must be an error") };
1663 assert!(matches!(err, EthFilterError::ReceiptsUnavailable(100)), "{err:?}");
1664 }
1665
1666 #[tokio::test]
1667 async fn test_range_block_mode_provider_receipts() {
1668 let provider = MockEthProvider::default();
1669
1670 let header_1 = alloy_consensus::Header { number: 100, ..Default::default() };
1671 let header_2 = alloy_consensus::Header { number: 101, ..Default::default() };
1672 let header_3 = alloy_consensus::Header { number: 102, ..Default::default() };
1673
1674 let block_hash_1 = FixedBytes::random();
1675 let block_hash_2 = FixedBytes::random();
1676 let block_hash_3 = FixedBytes::random();
1677
1678 provider.add_header(block_hash_1, header_1.clone());
1679 provider.add_header(block_hash_2, header_2.clone());
1680 provider.add_header(block_hash_3, header_3.clone());
1681
1682 let mock_log = alloy_primitives::Log {
1684 address: alloy_primitives::Address::ZERO,
1685 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
1686 };
1687
1688 let receipt_100_1 = reth_ethereum_primitives::Receipt {
1689 tx_type: TxType::Legacy,
1690 cumulative_gas_used: 21_000,
1691 logs: vec![mock_log.clone()],
1692 success: true,
1693 };
1694 let receipt_100_2 = reth_ethereum_primitives::Receipt {
1695 tx_type: TxType::Eip1559,
1696 cumulative_gas_used: 42_000,
1697 logs: vec![mock_log.clone()],
1698 success: true,
1699 };
1700 let receipt_101_1 = reth_ethereum_primitives::Receipt {
1701 tx_type: TxType::Eip2930,
1702 cumulative_gas_used: 30_000,
1703 logs: vec![mock_log.clone()],
1704 success: false,
1705 };
1706
1707 provider.add_receipts(100, vec![receipt_100_1.clone(), receipt_100_2.clone()]);
1708 provider.add_receipts(101, vec![receipt_101_1.clone()]);
1709 provider.add_receipts(102, vec![]);
1711
1712 let eth_api = build_test_eth_api(provider);
1713
1714 let eth_filter =
1715 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1716 let filter_inner = eth_filter.inner;
1717
1718 let headers = vec![
1719 SealedHeader::new(header_1, block_hash_1),
1720 SealedHeader::new(header_2, block_hash_2),
1721 SealedHeader::new(header_3, block_hash_3),
1722 ];
1723
1724 let mut range_mode = RangeBlockMode {
1725 filter_inner,
1726 iter: headers.into_iter().peekable(),
1727 next: VecDeque::new(),
1728 max_range: 3, parallel: false,
1730
1731 pending_tasks: FuturesOrdered::new(),
1732 };
1733
1734 let result = range_mode.next().await;
1736 assert!(result.is_ok());
1737 let receipt_result = result.unwrap().unwrap();
1738
1739 assert_eq!(receipt_result.header.hash(), block_hash_1);
1740 assert_eq!(receipt_result.header.number, 100);
1741 assert_eq!(receipt_result.receipts.len(), 2);
1742
1743 assert_eq!(receipt_result.receipts[0].tx_type, receipt_100_1.tx_type);
1745 assert_eq!(
1746 receipt_result.receipts[0].cumulative_gas_used,
1747 receipt_100_1.cumulative_gas_used
1748 );
1749 assert_eq!(receipt_result.receipts[0].success, receipt_100_1.success);
1750
1751 assert_eq!(receipt_result.receipts[1].tx_type, receipt_100_2.tx_type);
1752 assert_eq!(
1753 receipt_result.receipts[1].cumulative_gas_used,
1754 receipt_100_2.cumulative_gas_used
1755 );
1756 assert_eq!(receipt_result.receipts[1].success, receipt_100_2.success);
1757
1758 let result2 = range_mode.next().await;
1760 assert!(result2.is_ok());
1761 let receipt_result2 = result2.unwrap().unwrap();
1762
1763 assert_eq!(receipt_result2.header.hash(), block_hash_2);
1764 assert_eq!(receipt_result2.header.number, 101);
1765 assert_eq!(receipt_result2.receipts.len(), 1);
1766
1767 assert_eq!(receipt_result2.receipts[0].tx_type, receipt_101_1.tx_type);
1769 assert_eq!(
1770 receipt_result2.receipts[0].cumulative_gas_used,
1771 receipt_101_1.cumulative_gas_used
1772 );
1773 assert_eq!(receipt_result2.receipts[0].success, receipt_101_1.success);
1774
1775 let result3 = range_mode.next().await;
1777 assert!(result3.is_ok());
1778 assert!(result3.unwrap().is_none());
1779 }
1780
1781 #[tokio::test]
1782 async fn test_range_block_mode_iterator_exhaustion() {
1783 let provider = MockEthProvider::default();
1784
1785 let header_100 = alloy_consensus::Header { number: 100, ..Default::default() };
1786 let header_101 = alloy_consensus::Header { number: 101, ..Default::default() };
1787
1788 let block_hash_100 = FixedBytes::random();
1789 let block_hash_101 = FixedBytes::random();
1790
1791 provider.add_header(block_hash_100, header_100.clone());
1793 provider.add_header(block_hash_101, header_101.clone());
1794
1795 let mock_receipt = reth_ethereum_primitives::Receipt {
1797 tx_type: TxType::Legacy,
1798 cumulative_gas_used: 21_000,
1799 logs: vec![],
1800 success: true,
1801 };
1802 provider.add_receipts(100, vec![mock_receipt.clone()]);
1803 provider.add_receipts(101, vec![mock_receipt.clone()]);
1804
1805 let eth_api = build_test_eth_api(provider);
1806
1807 let eth_filter =
1808 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1809 let filter_inner = eth_filter.inner;
1810
1811 let headers = vec![
1812 SealedHeader::new(header_100, block_hash_100),
1813 SealedHeader::new(header_101, block_hash_101),
1814 ];
1815
1816 let mut range_mode = RangeBlockMode {
1817 filter_inner,
1818 iter: headers.into_iter().peekable(),
1819 next: VecDeque::new(),
1820 max_range: 1,
1821 parallel: false,
1822
1823 pending_tasks: FuturesOrdered::new(),
1824 };
1825
1826 let result1 = range_mode.next().await;
1827 assert!(result1.is_ok());
1828 assert!(result1.unwrap().is_some()); assert!(range_mode.iter.peek().is_some()); let result2 = range_mode.next().await;
1833 assert!(result2.is_ok());
1834 assert!(result2.unwrap().is_some()); assert!(range_mode.iter.peek().is_none());
1838
1839 let result3 = range_mode.next().await;
1841 assert!(result3.is_ok());
1842 assert!(result3.unwrap().is_none());
1843 }
1844
1845 #[tokio::test]
1846 async fn test_cached_mode_with_mock_receipts() {
1847 let test_hash = FixedBytes::from([42u8; 32]);
1849 let test_block_number = 100u64;
1850 let test_header = SealedHeader::new(
1851 alloy_consensus::Header {
1852 number: test_block_number,
1853 gas_used: 50_000,
1854 ..Default::default()
1855 },
1856 test_hash,
1857 );
1858
1859 let mock_log = alloy_primitives::Log {
1861 address: alloy_primitives::Address::ZERO,
1862 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
1863 };
1864
1865 let mock_receipt = reth_ethereum_primitives::Receipt {
1866 tx_type: TxType::Legacy,
1867 cumulative_gas_used: 21_000,
1868 logs: vec![mock_log],
1869 success: true,
1870 };
1871
1872 let provider = MockEthProvider::default();
1873 provider.add_header(test_hash, test_header.header().clone());
1874 provider.add_receipts(test_block_number, vec![mock_receipt.clone()]);
1875
1876 let eth_api = build_test_eth_api(provider);
1877 let eth_filter =
1878 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1879 let filter_inner = eth_filter.inner;
1880
1881 let headers = vec![test_header.clone()];
1882
1883 let mut cached_mode = CachedMode { filter_inner, headers_iter: headers.into_iter() };
1884
1885 let result = cached_mode.next().await.expect("next should succeed");
1887 let receipt_block_result = result.expect("should have receipt result");
1888 assert_eq!(receipt_block_result.header.hash(), test_hash);
1889 assert_eq!(receipt_block_result.header.number, test_block_number);
1890 assert_eq!(receipt_block_result.receipts.len(), 1);
1891 assert_eq!(receipt_block_result.receipts[0].tx_type, mock_receipt.tx_type);
1892 assert_eq!(
1893 receipt_block_result.receipts[0].cumulative_gas_used,
1894 mock_receipt.cumulative_gas_used
1895 );
1896 assert_eq!(receipt_block_result.receipts[0].success, mock_receipt.success);
1897
1898 let result2 = cached_mode.next().await;
1900 assert!(result2.is_ok());
1901 assert!(result2.unwrap().is_none());
1902 }
1903
1904 #[tokio::test]
1905 async fn test_cached_mode_empty_headers() {
1906 let provider = MockEthProvider::default();
1907 let eth_api = build_test_eth_api(provider);
1908
1909 let eth_filter =
1910 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1911 let filter_inner = eth_filter.inner;
1912
1913 let headers: Vec<SealedHeader<alloy_consensus::Header>> = vec![];
1914
1915 let mut cached_mode = CachedMode { filter_inner, headers_iter: headers.into_iter() };
1916
1917 let result = cached_mode.next().await.expect("next should succeed");
1919 assert!(result.is_none());
1920 }
1921
1922 #[tokio::test]
1923 async fn test_log_limit_retry_range_excludes_overflow_block() {
1924 let provider = MockEthProvider::default();
1925
1926 use alloy_consensus::TxLegacy;
1927 use reth_db_api::models::StoredBlockBodyIndices;
1928 use reth_ethereum_primitives::{TransactionSigned, TxType};
1929
1930 let tx_inner = TxLegacy {
1931 chain_id: Some(1),
1932 nonce: 0,
1933 gas_price: 21_000,
1934 gas_limit: 21_000,
1935 to: alloy_primitives::TxKind::Call(alloy_primitives::Address::ZERO),
1936 value: alloy_primitives::U256::ZERO,
1937 input: alloy_primitives::Bytes::new(),
1938 };
1939 let signature = alloy_primitives::Signature::test_signature();
1940 let tx = TransactionSigned::new_unhashed(tx_inner.into(), signature);
1941
1942 let mock_log = alloy_primitives::Log {
1943 address: alloy_primitives::Address::ZERO,
1944 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
1945 };
1946
1947 let receipt = reth_ethereum_primitives::Receipt {
1948 tx_type: TxType::Legacy,
1949 cumulative_gas_used: 21_000,
1950 logs: vec![mock_log],
1951 success: true,
1952 };
1953
1954 let mut prev_hash = alloy_primitives::B256::default();
1955 for (idx, block_number) in (100u64..=102).enumerate() {
1956 let header = alloy_consensus::Header {
1957 number: block_number,
1958 parent_hash: prev_hash,
1959 logs_bloom: Bloom::repeat_byte(1u8),
1960 ..Default::default()
1961 };
1962 let hash = header.hash_slow();
1963 prev_hash = hash;
1964
1965 let block = reth_ethereum_primitives::Block {
1966 header,
1967 body: reth_ethereum_primitives::BlockBody {
1968 transactions: vec![tx.clone()],
1969 ..Default::default()
1970 },
1971 };
1972 provider.add_block(hash, block);
1973 provider.add_receipts(block_number, vec![receipt.clone()]);
1974 provider.add_block_body_indices(
1975 block_number,
1976 StoredBlockBodyIndices { first_tx_num: idx as u64, tx_count: 1 },
1977 );
1978 }
1979
1980 let eth_api = build_test_eth_api(provider);
1981 let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1982 let err = eth_filter
1983 .inner
1984 .clone()
1985 .get_logs_in_block_range(
1986 Filter::default(),
1987 100,
1988 102,
1989 QueryLimits { max_blocks_per_filter: None, max_logs_per_response: Some(2) },
1990 )
1991 .await
1992 .expect_err("range should exceed max logs");
1993
1994 let EthFilterError::QueryExceedsMaxResults { max_logs, from_block, to_block } = err else {
1995 panic!("unexpected error: {err:?}");
1996 };
1997
1998 assert_eq!(max_logs, 2);
1999 assert_eq!(from_block, 100);
2000 assert_eq!(to_block, 101);
2001 }
2002
2003 #[tokio::test]
2004 async fn test_non_consecutive_headers_after_bloom_filter() {
2005 let provider = MockEthProvider::default();
2006
2007 let mut expected_hashes = vec![];
2009 let mut prev_hash = alloy_primitives::B256::default();
2010
2011 use alloy_consensus::TxLegacy;
2013 use reth_ethereum_primitives::{TransactionSigned, TxType};
2014
2015 let tx_inner = TxLegacy {
2016 chain_id: Some(1),
2017 nonce: 0,
2018 gas_price: 21_000,
2019 gas_limit: 21_000,
2020 to: alloy_primitives::TxKind::Call(alloy_primitives::Address::ZERO),
2021 value: alloy_primitives::U256::ZERO,
2022 input: alloy_primitives::Bytes::new(),
2023 };
2024 let signature = alloy_primitives::Signature::test_signature();
2025 let tx = TransactionSigned::new_unhashed(tx_inner.into(), signature);
2026
2027 for i in 100u64..=103 {
2028 let header = alloy_consensus::Header {
2029 number: i,
2030 parent_hash: prev_hash,
2031 logs_bloom: if i == 100 || i == 102 {
2033 Bloom::repeat_byte(1u8)
2034 } else {
2035 alloy_primitives::Bloom::default()
2036 },
2037 ..Default::default()
2038 };
2039
2040 let hash = header.hash_slow();
2041 expected_hashes.push(hash);
2042 prev_hash = hash;
2043
2044 let transactions = if i == 100 || i == 102 { vec![tx.clone()] } else { vec![] };
2046
2047 let block = reth_ethereum_primitives::Block {
2048 header,
2049 body: reth_ethereum_primitives::BlockBody { transactions, ..Default::default() },
2050 };
2051 provider.add_block(hash, block);
2052 }
2053
2054 let mock_log = alloy_primitives::Log {
2056 address: alloy_primitives::Address::ZERO,
2057 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
2058 };
2059
2060 let receipt = reth_ethereum_primitives::Receipt {
2061 tx_type: TxType::Legacy,
2062 cumulative_gas_used: 21_000,
2063 logs: vec![mock_log],
2064 success: true,
2065 };
2066
2067 provider.add_receipts(100, vec![receipt.clone()]);
2068 provider.add_receipts(101, vec![]);
2069 provider.add_receipts(102, vec![receipt.clone()]);
2070 provider.add_receipts(103, vec![]);
2071
2072 use reth_db_api::models::StoredBlockBodyIndices;
2074 provider
2075 .add_block_body_indices(100, StoredBlockBodyIndices { first_tx_num: 0, tx_count: 1 });
2076 provider
2077 .add_block_body_indices(101, StoredBlockBodyIndices { first_tx_num: 1, tx_count: 0 });
2078 provider
2079 .add_block_body_indices(102, StoredBlockBodyIndices { first_tx_num: 1, tx_count: 1 });
2080 provider
2081 .add_block_body_indices(103, StoredBlockBodyIndices { first_tx_num: 2, tx_count: 0 });
2082
2083 let eth_api = build_test_eth_api(provider);
2084 let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
2085
2086 let filter = Filter::default();
2088
2089 let logs = eth_filter
2091 .inner
2092 .clone()
2093 .get_logs_in_block_range(filter, 100, 103, QueryLimits::default())
2094 .await
2095 .expect("should succeed");
2096
2097 assert_eq!(logs.len(), 2);
2099
2100 assert_eq!(logs[0].block_number, Some(100));
2101 assert_eq!(logs[1].block_number, Some(102));
2102
2103 assert_eq!(logs[0].block_hash, Some(expected_hashes[0])); assert_eq!(logs[1].block_hash, Some(expected_hashes[2])); }
2107
2108 #[tokio::test]
2109 async fn test_range_scan_waits_for_blocking_io_permit() {
2110 use reth_rpc_eth_api::helpers::SpawnBlocking;
2111
2112 let provider = MockEthProvider::default();
2113 let header = alloy_consensus::Header::default();
2114 provider.add_header(header.hash_slow(), header);
2115 provider.add_receipts(0, vec![]);
2116 let eth_api = build_test_eth_api(provider);
2117
2118 let guard = eth_api.blocking_io_task_guard().clone();
2120 let permits =
2121 guard.clone().acquire_many_owned(guard.available_permits() as u32).await.unwrap();
2122
2123 let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
2124 let scan = eth_filter.inner.clone().get_logs_in_block_range(
2125 Filter::default(),
2126 0,
2127 0,
2128 QueryLimits::default(),
2129 );
2130 tokio::pin!(scan);
2131 assert!(
2132 tokio::time::timeout(Duration::from_millis(100), &mut scan).await.is_err(),
2133 "scan must wait for a blocking IO permit"
2134 );
2135
2136 drop(permits);
2137 assert!(scan.await.unwrap().is_empty());
2138 }
2139
2140 #[tokio::test]
2141 async fn test_range_scan_across_windows() {
2142 use alloy_consensus::TxLegacy;
2143 use alloy_primitives::{Address, Bloom, Bytes, Log, LogData, Signature};
2144 use reth_db_api::models::StoredBlockBodyIndices;
2145 use reth_ethereum_primitives::{Block, BlockBody, Receipt, TransactionSigned};
2146
2147 let provider = MockEthProvider::default();
2148 let tx = TransactionSigned::new_unhashed(
2149 TxLegacy {
2150 chain_id: Some(1),
2151 gas_price: 21_000,
2152 gas_limit: 21_000,
2153 ..Default::default()
2154 }
2155 .into(),
2156 Signature::test_signature(),
2157 );
2158 let receipt = Receipt {
2159 tx_type: TxType::Legacy,
2160 cumulative_gas_used: 21_000,
2161 logs: vec![Log {
2162 address: Address::ZERO,
2163 data: LogData::new_unchecked(vec![], Bytes::new()),
2164 }],
2165 success: true,
2166 };
2167
2168 let matching_blocks = [5u64, 1_200, 2_400];
2171 let mut parent_hash = FixedBytes::default();
2172 for number in 0..=2_500u64 {
2173 let matches = matching_blocks.contains(&number);
2174 let header = alloy_consensus::Header {
2175 number,
2176 parent_hash,
2177 logs_bloom: if matches { Bloom::repeat_byte(1u8) } else { Bloom::default() },
2178 ..Default::default()
2179 };
2180 parent_hash = header.hash_slow();
2181 let transactions = if matches { vec![tx.clone()] } else { Vec::new() };
2182 provider.add_block(
2183 parent_hash,
2184 Block { header, body: BlockBody { transactions, ..Default::default() } },
2185 );
2186 if matches {
2187 let tx_num = matching_blocks.iter().position(|b| *b == number).unwrap() as u64;
2188 provider.add_receipts(number, vec![receipt.clone()]);
2189 provider.add_block_body_indices(
2190 number,
2191 StoredBlockBodyIndices { first_tx_num: tx_num, tx_count: 1 },
2192 );
2193 } else {
2194 provider.add_receipts(number, vec![]);
2195 }
2196 }
2197
2198 let eth_filter = EthFilter::new(
2199 build_test_eth_api(provider),
2200 EthFilterConfig::default(),
2201 Runtime::test(),
2202 );
2203 let logs = eth_filter
2204 .inner
2205 .clone()
2206 .get_logs_in_block_range(Filter::default(), 0, 2_500, QueryLimits::default())
2207 .await
2208 .unwrap();
2209 let blocks = logs.iter().map(|log| log.block_number.unwrap()).collect::<Vec<_>>();
2210 assert_eq!(blocks, matching_blocks);
2211
2212 let err = eth_filter
2215 .inner
2216 .clone()
2217 .get_logs_in_block_range(
2218 Filter::default(),
2219 0,
2220 2_500,
2221 QueryLimits { max_blocks_per_filter: None, max_logs_per_response: Some(1) },
2222 )
2223 .await
2224 .unwrap_err();
2225 assert!(
2226 matches!(
2227 err,
2228 EthFilterError::QueryExceedsMaxResults {
2229 max_logs: 1,
2230 from_block: 0,
2231 to_block: 1_199
2232 }
2233 ),
2234 "{err:?}"
2235 );
2236 }
2237
2238 #[tokio::test]
2239 async fn test_logs_for_filter_pending_to_block_ends_at_head() {
2240 let provider = MockEthProvider::default();
2241 let header = alloy_consensus::Header { number: 2, ..Default::default() };
2242 let hash = header.hash_slow();
2243 provider.add_header(hash, header);
2244 provider.add_receipts(2, vec![]);
2245 provider.set_pending_block_num_hash(Some(alloy_eips::BlockNumHash::new(3, hash)));
2247
2248 let eth_filter = EthFilter::new(
2249 build_test_eth_api(provider),
2250 EthFilterConfig::default(),
2251 Runtime::test(),
2252 );
2253 for filter in [
2254 Filter::new().from_block(0u64).to_block(BlockNumberOrTag::Pending),
2255 Filter::new().select(BlockNumberOrTag::Pending..),
2256 ] {
2257 let logs = eth_filter
2258 .inner
2259 .clone()
2260 .logs_for_filter(filter, QueryLimits::default())
2261 .await
2262 .unwrap();
2263 assert!(logs.is_empty());
2264 }
2265 }
2266
2267 #[tokio::test]
2268 async fn test_logs_for_filter_over_pruned_receipts() {
2269 let provider = MockEthProvider::default();
2270 let mut parent_hash = FixedBytes::default();
2271 for number in 0..=2u64 {
2272 let header = alloy_consensus::Header {
2273 number,
2274 parent_hash,
2275 logs_bloom: Bloom::repeat_byte(1u8),
2276 ..Default::default()
2277 };
2278 parent_hash = header.hash_slow();
2279 provider.add_block(
2280 parent_hash,
2281 reth_ethereum_primitives::Block { header, body: Default::default() },
2282 );
2283 if number != 1 {
2285 provider.add_receipts(number, vec![]);
2286 }
2287 }
2288
2289 let eth_filter = EthFilter::new(
2290 build_test_eth_api(provider),
2291 EthFilterConfig::default(),
2292 Runtime::test(),
2293 );
2294
2295 let err = eth_filter
2297 .inner
2298 .clone()
2299 .logs_for_filter(Filter::new().from_block(0u64).to_block(2u64), QueryLimits::default())
2300 .await
2301 .unwrap_err();
2302 assert!(matches!(err, EthFilterError::ReceiptsUnavailable(1)), "{err:?}");
2303
2304 let logs = eth_filter
2306 .inner
2307 .clone()
2308 .logs_for_filter(Filter::new().from_block(2u64).to_block(2u64), QueryLimits::default())
2309 .await
2310 .unwrap();
2311 assert!(logs.is_empty());
2312 }
2313}