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
345impl<Eth> EthFilterApiServer<RpcTransaction<Eth::NetworkTypes>, RpcLog<Eth::NetworkTypes>>
346 for EthFilter<Eth>
347where
348 Eth: FullEthApiTypes + LoadReceipt + EthBlocks,
349{
350 async fn new_filter(&self, filter: Filter) -> RpcResult<FilterId> {
352 trace!(target: "rpc::eth", "Serving eth_newFilter");
353 self.inner
354 .install_filter(FilterKind::<RpcTransaction<Eth::NetworkTypes>>::Log(Box::new(filter)))
355 .await
356 }
357
358 async fn new_block_filter(&self) -> RpcResult<FilterId> {
360 trace!(target: "rpc::eth", "Serving eth_newBlockFilter");
361 self.inner.install_filter(FilterKind::<RpcTransaction<Eth::NetworkTypes>>::Block).await
362 }
363
364 async fn new_pending_transaction_filter(
366 &self,
367 kind: Option<PendingTransactionFilterKind>,
368 ) -> RpcResult<FilterId> {
369 trace!(target: "rpc::eth", "Serving eth_newPendingTransactionFilter");
370
371 let transaction_kind = match kind.unwrap_or_default() {
372 PendingTransactionFilterKind::Hashes => {
373 let receiver = self.pool().pending_transactions_listener();
374 let pending_txs_receiver = PendingTransactionsReceiver::new(receiver);
375 FilterKind::PendingTransaction(PendingTransactionKind::Hashes(pending_txs_receiver))
376 }
377 PendingTransactionFilterKind::Full => {
378 let stream = self.pool().new_pending_pool_transactions_listener();
379 let full_txs_receiver = FullTransactionsReceiver::new(
380 stream,
381 dyn_clone::clone(self.inner.eth_api.converter()),
382 );
383 FilterKind::PendingTransaction(PendingTransactionKind::FullTransaction(Arc::new(
384 full_txs_receiver,
385 )))
386 }
387 };
388
389 self.inner.install_filter(transaction_kind).await
391 }
392
393 async fn filter_changes(
395 &self,
396 id: FilterId,
397 ) -> RpcResult<FilterChanges<RpcTransaction<Eth::NetworkTypes>, RpcLog<Eth::NetworkTypes>>>
398 {
399 trace!(target: "rpc::eth", "Serving eth_getFilterChanges");
400 Ok(Self::filter_changes(self, id).await?)
401 }
402
403 async fn filter_logs(&self, id: FilterId) -> RpcResult<Vec<RpcLog<Eth::NetworkTypes>>> {
409 trace!(target: "rpc::eth", "Serving eth_getFilterLogs");
410 Ok(Self::filter_logs(self, id).await?)
411 }
412
413 async fn uninstall_filter(&self, id: FilterId) -> RpcResult<bool> {
415 trace!(target: "rpc::eth", "Serving eth_uninstallFilter");
416 let mut filters = self.inner.active_filters.inner.lock().await;
417 if filters.remove(&id).is_some() {
418 trace!(target: "rpc::eth::filter", ?id, "uninstalled filter");
419 Ok(true)
420 } else {
421 Ok(false)
422 }
423 }
424
425 async fn logs(&self, filter: Filter) -> RpcResult<Vec<RpcLog<Eth::NetworkTypes>>> {
429 trace!(target: "rpc::eth", "Serving eth_getLogs");
430 Ok(self.logs_for_filter(filter, self.inner.query_limits).await?)
431 }
432}
433
434impl<Eth> std::fmt::Debug for EthFilter<Eth>
435where
436 Eth: EthApiTypes,
437{
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 f.debug_struct("EthFilter").finish_non_exhaustive()
440 }
441}
442
443#[derive(Debug)]
445struct EthFilterInner<Eth: EthApiTypes> {
446 eth_api: Eth,
448 active_filters: ActiveFilters<RpcTransaction<Eth::NetworkTypes>>,
450 id_provider: Arc<dyn IdProvider>,
452 query_limits: QueryLimits,
454 max_headers_range: u64,
456 task_spawner: Runtime,
458 stale_filter_ttl: Duration,
460}
461
462impl<Eth> EthFilterInner<Eth>
463where
464 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool>
465 + EthApiTypes<NetworkTypes: reth_rpc_eth_api::types::RpcTypes>
466 + LoadReceipt
467 + EthBlocks,
468{
469 fn provider(&self) -> &Eth::Provider {
471 self.eth_api.provider()
472 }
473
474 fn eth_cache(&self) -> &EthStateCache<Eth::Primitives> {
476 self.eth_api.cache()
477 }
478
479 async fn logs_for_filter(
481 self: Arc<Self>,
482 filter: Filter,
483 limits: QueryLimits,
484 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
485 match filter.block_option {
486 FilterBlockOption::AtBlockHash(block_hash) => {
487 let Some((receipts, maybe_block)) =
489 self.eth_cache().get_receipts_and_maybe_block(block_hash).await?
490 else {
491 return Err(match self.provider().block_number(block_hash)? {
493 Some(number) => {
494 let earliest_available = self.provider().earliest_block_number()?;
495 if number < earliest_available {
496 EthApiError::PrunedHistoryUnavailable {
497 requested: number,
498 earliest_available,
499 }
500 .into()
501 } else {
502 EthFilterError::ReceiptsUnavailable(number)
503 }
504 }
505 None => ProviderError::HeaderNotFound(block_hash.into()).into(),
506 })
507 };
508
509 let header = if let Some(block) = &maybe_block {
510 block.clone_sealed_header()
511 } else {
512 let header = self
513 .provider()
514 .header_by_hash_or_number(block_hash.into())?
515 .ok_or_else(|| ProviderError::HeaderNotFound(block_hash.into()))?;
516 SealedHeader::new(header, block_hash)
517 };
518
519 let earliest_block = self.provider().earliest_block_number()?;
521 if header.number() < earliest_block {
522 return Err(EthApiError::PrunedHistoryUnavailable {
523 requested: header.number(),
524 earliest_available: earliest_block,
525 }
526 .into());
527 }
528
529 if !filter.matches_bloom(header.logs_bloom()) {
530 return Ok(Vec::new())
531 }
532
533 let mut all_logs = Vec::new();
534 append_matching_block_logs(
535 &mut all_logs,
536 self.eth_api.converter(),
537 maybe_block
538 .map(ProviderOrBlock::Block)
539 .unwrap_or_else(|| ProviderOrBlock::Provider(self.provider())),
540 &filter,
541 &header,
542 &receipts,
543 false,
544 )?;
545 Ok(all_logs)
546 }
547 FilterBlockOption::Range { from_block, to_block } => {
548 if from_block.is_some_and(|b| b.is_pending()) {
550 let to_block = to_block.unwrap_or(BlockNumberOrTag::Pending);
551 if !(to_block.is_pending() || to_block.is_number()) {
552 return Ok(Vec::new());
554 }
555 if let Ok(Some(pending_block)) = self.eth_api.local_pending_block().await {
557 if let BlockNumberOrTag::Number(to_block) = to_block &&
558 to_block < pending_block.block.number()
559 {
560 return Ok(Vec::new());
562 }
563
564 let info = self.provider().chain_info()?;
565 if pending_block.block.number() > info.best_number {
566 let mut all_logs = Vec::new();
568 let header = pending_block.block.clone_sealed_header();
569 append_matching_block_logs(
570 &mut all_logs,
571 self.eth_api.converter(),
572 ProviderOrBlock::<Eth::Provider>::Block(pending_block.block),
573 &filter,
574 &header,
575 &pending_block.receipts,
576 false, )?;
578 return Ok(all_logs)
579 }
580 }
581 }
582
583 let info = self.provider().chain_info()?;
584 let start_block = info.best_number;
585 let from = from_block
588 .filter(|num| !num.is_pending())
589 .map(|num| self.provider().convert_block_number(num))
590 .transpose()?
591 .flatten();
592 let to = to_block
593 .filter(|num| !num.is_pending())
594 .map(|num| self.provider().convert_block_number(num))
595 .transpose()?
596 .flatten();
597
598 if let Some(t) = to &&
600 t > info.best_number
601 {
602 return Err(EthFilterError::BlockRangeExceedsHead {
603 requested: t,
604 head: info.best_number,
605 });
606 }
607
608 let (from_block_number, to_block_number) =
609 logs_utils::get_filter_block_range(from, to, start_block, info)?;
610
611 let earliest_block = self.provider().earliest_block_number()?;
613 if from_block_number < earliest_block {
614 return Err(EthApiError::PrunedHistoryUnavailable {
615 requested: from_block_number,
616 earliest_available: earliest_block,
617 }
618 .into());
619 }
620
621 self.get_logs_in_block_range(filter, from_block_number, to_block_number, limits)
622 .await
623 }
624 }
625 }
626
627 async fn install_filter(
629 &self,
630 kind: FilterKind<RpcTransaction<Eth::NetworkTypes>>,
631 ) -> RpcResult<FilterId> {
632 let last_poll_block_number = self.provider().best_block_number().to_rpc_result()?;
633 let subscription_id = self.id_provider.next_id();
634
635 let id = match subscription_id {
636 jsonrpsee_types::SubscriptionId::Num(n) => FilterId::Num(n),
637 jsonrpsee_types::SubscriptionId::Str(s) => FilterId::Str(s.into_owned()),
638 };
639 let mut filters = self.active_filters.inner.lock().await;
640 filters.insert(
641 id.clone(),
642 ActiveFilter {
643 block: last_poll_block_number,
644 last_poll_timestamp: Instant::now(),
645 kind,
646 },
647 );
648 Ok(id)
649 }
650
651 async fn get_logs_in_block_range(
657 self: Arc<Self>,
658 filter: Filter,
659 from_block: u64,
660 to_block: u64,
661 limits: QueryLimits,
662 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
663 trace!(target: "rpc::eth::filter", from=from_block, to=to_block, ?filter, "finding logs in range");
664
665 if to_block < from_block {
667 return Err(EthFilterError::InvalidBlockRangeParams)
668 }
669
670 if let Some(max_blocks_per_filter) =
671 limits.max_blocks_per_filter.filter(|limit| to_block - from_block > *limit)
672 {
673 return Err(EthFilterError::QueryExceedsMaxBlocks(max_blocks_per_filter))
674 }
675
676 let permit = self
680 .eth_api
681 .acquire_owned_blocking_io()
682 .await
683 .map_err(|_| EthFilterError::InternalError)?;
684
685 let (mut tx, rx) = oneshot::channel();
686 let this = self.clone();
687 self.task_spawner.spawn_blocking_task(async move {
688 let _permit = permit;
689 let fut = this.get_logs_in_block_range_inner(&filter, from_block, to_block, limits);
690 tokio::pin!(fut);
691 let res = tokio::select! {
692 biased;
694 _ = tx.closed() => None,
695 res = &mut fut => Some(res),
696 };
697 if let Some(res) = res {
698 let _ = tx.send(res);
699 }
700 });
701
702 rx.await.map_err(|_| EthFilterError::InternalError)?
703 }
704
705 async fn get_logs_in_block_range_inner(
714 self: Arc<Self>,
715 filter: &Filter,
716 from_block: u64,
717 to_block: u64,
718 limits: QueryLimits,
719 ) -> Result<Vec<RpcLog<Eth::NetworkTypes>>, EthFilterError> {
720 let mut all_logs = Vec::new();
721
722 let chain_tip = self.provider().best_block_number()?;
724
725 for (from, to) in
729 BlockRangeInclusiveIter::new(from_block..=to_block, self.max_headers_range)
730 {
731 tokio::task::yield_now().await;
733
734 let mut matching_headers = Vec::new();
736 let headers = self.provider().headers_range(from..=to)?;
737
738 let mut headers_iter = headers.into_iter().peekable();
739
740 while let Some(header) = headers_iter.next() {
741 if !filter.matches_bloom(header.logs_bloom()) {
742 continue
743 }
744
745 let current_number = header.number();
746
747 let block_hash = match headers_iter.peek() {
748 Some(next_header) if next_header.number() == current_number + 1 => {
749 next_header.parent_hash()
751 }
752 _ => {
753 header.hash_slow()
755 }
756 };
757
758 matching_headers.push(SealedHeader::new(header, block_hash));
759 }
760
761 let mut range_mode = RangeMode::new(
763 self.clone(),
764 matching_headers,
765 from_block,
766 to_block,
767 self.max_headers_range,
768 chain_tip,
769 );
770
771 while let Some(ReceiptBlockResult { receipts, recovered_block, header }) =
773 range_mode.next().await?
774 {
775 let num_hash = header.num_hash();
776 append_matching_block_logs(
777 &mut all_logs,
778 self.eth_api.converter(),
779 recovered_block
780 .map(ProviderOrBlock::Block)
781 .unwrap_or_else(|| ProviderOrBlock::Provider(self.provider())),
782 filter,
783 &header,
784 &receipts,
785 false,
786 )?;
787
788 let is_multi_block_range = from_block != to_block;
791 if let Some(max_logs_per_response) = limits.max_logs_per_response &&
792 is_multi_block_range &&
793 all_logs.len() > max_logs_per_response
794 {
795 let retry_to_block = if num_hash.number == from_block {
796 from_block
797 } else {
798 num_hash.number - 1
799 };
800
801 debug!(
802 target: "rpc::eth::filter",
803 logs_found = all_logs.len(),
804 max_logs_per_response,
805 from_block,
806 to_block = retry_to_block,
807 "Query exceeded max logs per response limit"
808 );
809 return Err(EthFilterError::QueryExceedsMaxResults {
810 max_logs: max_logs_per_response,
811 from_block,
812 to_block: retry_to_block,
813 });
814 }
815 }
816 }
817
818 Ok(all_logs)
819 }
820}
821
822#[derive(Debug, Clone, Default)]
824pub struct ActiveFilters<T> {
825 inner: Arc<Mutex<HashMap<FilterId, ActiveFilter<T>>>>,
826}
827
828impl<T> ActiveFilters<T> {
829 pub fn new() -> Self {
831 Self { inner: Arc::new(Mutex::new(HashMap::default())) }
832 }
833
834 pub async fn contains(&self, id: &FilterId) -> bool {
836 self.inner.lock().await.contains_key(id)
837 }
838
839 pub async fn len(&self) -> usize {
841 self.inner.lock().await.len()
842 }
843
844 pub async fn is_empty(&self) -> bool {
846 self.inner.lock().await.is_empty()
847 }
848
849 pub async fn ids(&self) -> Vec<FilterId> {
851 self.inner.lock().await.keys().cloned().collect()
852 }
853}
854
855#[derive(Debug)]
857struct ActiveFilter<T> {
858 block: u64,
860 last_poll_timestamp: Instant,
862 kind: FilterKind<T>,
864}
865
866#[derive(Debug, Clone)]
868struct PendingTransactionsReceiver {
869 txs_receiver: Arc<Mutex<Receiver<TxHash>>>,
870}
871
872impl PendingTransactionsReceiver {
873 fn new(receiver: Receiver<TxHash>) -> Self {
874 Self { txs_receiver: Arc::new(Mutex::new(receiver)) }
875 }
876
877 async fn drain<T>(&self) -> FilterChanges<T> {
879 let mut pending_txs = Vec::new();
880 let mut prepared_stream = self.txs_receiver.lock().await;
881
882 while let Ok(tx_hash) = prepared_stream.try_recv() {
883 pending_txs.push(tx_hash);
884 }
885
886 FilterChanges::Hashes(pending_txs)
888 }
889}
890
891#[derive(Debug, Clone)]
893struct FullTransactionsReceiver<T: PoolTransaction, TxCompat> {
894 txs_stream: Arc<Mutex<NewSubpoolTransactionStream<T>>>,
895 converter: TxCompat,
896}
897
898impl<T, TxCompat> FullTransactionsReceiver<T, TxCompat>
899where
900 T: PoolTransaction + 'static,
901 TxCompat: RpcConvert<Primitives: NodePrimitives<SignedTx = T::Consensus>>,
902{
903 fn new(stream: NewSubpoolTransactionStream<T>, converter: TxCompat) -> Self {
905 Self { txs_stream: Arc::new(Mutex::new(stream)), converter }
906 }
907
908 async fn drain(&self) -> FilterChanges<RpcTransaction<TxCompat::Network>> {
910 let mut pending_txs = Vec::new();
911 let mut prepared_stream = self.txs_stream.lock().await;
912
913 while let Ok(tx) = prepared_stream.try_recv() {
914 match self.converter.fill_pending(tx.transaction.to_consensus()) {
915 Ok(tx) => pending_txs.push(tx),
916 Err(err) => {
917 error!(target: "rpc",
918 %err,
919 "Failed to fill txn with block context"
920 );
921 }
922 }
923 }
924 FilterChanges::Transactions(pending_txs)
925 }
926}
927
928#[async_trait]
930trait FullTransactionsFilter<T>: fmt::Debug + Send + Sync + Unpin + 'static {
931 async fn drain(&self) -> FilterChanges<T>;
932}
933
934#[async_trait]
935impl<T, TxCompat> FullTransactionsFilter<RpcTransaction<TxCompat::Network>>
936 for FullTransactionsReceiver<T, TxCompat>
937where
938 T: PoolTransaction + 'static,
939 TxCompat: RpcConvert<Primitives: NodePrimitives<SignedTx = T::Consensus>>,
940{
941 async fn drain(&self) -> FilterChanges<RpcTransaction<TxCompat::Network>> {
942 Self::drain(self).await
943 }
944}
945
946#[derive(Debug, Clone)]
952enum PendingTransactionKind<T> {
953 Hashes(PendingTransactionsReceiver),
954 FullTransaction(Arc<dyn FullTransactionsFilter<T>>),
955}
956
957impl<T: 'static> PendingTransactionKind<T> {
958 async fn drain(&self) -> FilterChanges<T> {
959 match self {
960 Self::Hashes(receiver) => receiver.drain().await,
961 Self::FullTransaction(receiver) => receiver.drain().await,
962 }
963 }
964}
965
966#[derive(Clone, Debug)]
967enum FilterKind<T> {
968 Log(Box<Filter>),
969 Block,
970 PendingTransaction(PendingTransactionKind<T>),
971}
972
973#[derive(Debug)]
975struct BlockRangeInclusiveIter {
976 iter: StepBy<RangeInclusive<u64>>,
977 step: u64,
978 end: u64,
979}
980
981impl BlockRangeInclusiveIter {
982 fn new(range: RangeInclusive<u64>, step: u64) -> Self {
983 Self { end: *range.end(), iter: range.step_by(step as usize + 1), step }
984 }
985}
986
987impl Iterator for BlockRangeInclusiveIter {
988 type Item = (u64, u64);
989
990 fn next(&mut self) -> Option<Self::Item> {
991 let start = self.iter.next()?;
992 let end = (start + self.step).min(self.end);
993 if start > end {
994 return None
995 }
996 Some((start, end))
997 }
998}
999
1000#[derive(Debug, thiserror::Error)]
1002pub enum EthFilterError {
1003 #[error("filter not found")]
1005 FilterNotFound(FilterId),
1006 #[error("invalid block range params")]
1008 InvalidBlockRangeParams,
1009 #[error("block range extends beyond current head block: requested {requested}, head {head}")]
1011 BlockRangeExceedsHead {
1012 requested: u64,
1014 head: u64,
1016 },
1017 #[error("query exceeds max block range {0}")]
1019 QueryExceedsMaxBlocks(u64),
1020 #[error("pruned history unavailable")]
1022 ReceiptsUnavailable(u64),
1023 #[error("query exceeds max results {max_logs}, retry with the range {from_block}-{to_block}")]
1025 QueryExceedsMaxResults {
1026 max_logs: usize,
1028 from_block: u64,
1030 to_block: u64,
1032 },
1033 #[error(transparent)]
1035 EthAPIError(#[from] EthApiError),
1036 #[error("internal filter error")]
1038 InternalError,
1039}
1040
1041impl From<EthFilterError> for jsonrpsee::types::error::ErrorObject<'static> {
1042 fn from(err: EthFilterError) -> Self {
1043 match err {
1044 EthFilterError::FilterNotFound(_) => rpc_error_with_code(
1046 jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE,
1047 "filter not found",
1048 ),
1049 err @ EthFilterError::InternalError => {
1050 rpc_error_with_code(jsonrpsee::types::error::INTERNAL_ERROR_CODE, err.to_string())
1051 }
1052 EthFilterError::EthAPIError(err) => err.into(),
1053 err @ EthFilterError::ReceiptsUnavailable(_) => {
1054 rpc_error_with_code(EthRpcErrorCode::PrunedHistory.code(), err.to_string())
1055 }
1056 err @ (EthFilterError::InvalidBlockRangeParams |
1057 EthFilterError::QueryExceedsMaxBlocks(_) |
1058 EthFilterError::QueryExceedsMaxResults { .. } |
1059 EthFilterError::BlockRangeExceedsHead { .. }) => {
1060 rpc_error_with_code(jsonrpsee::types::error::INVALID_PARAMS_CODE, err.to_string())
1061 }
1062 }
1063 }
1064}
1065
1066impl From<ProviderError> for EthFilterError {
1067 fn from(err: ProviderError) -> Self {
1068 Self::EthAPIError(err.into())
1069 }
1070}
1071
1072impl From<logs_utils::FilterBlockRangeError> for EthFilterError {
1073 fn from(err: logs_utils::FilterBlockRangeError) -> Self {
1074 match err {
1075 logs_utils::FilterBlockRangeError::InvalidBlockRange => Self::InvalidBlockRangeParams,
1076 logs_utils::FilterBlockRangeError::BlockRangeExceedsHead { requested, head } => {
1077 Self::BlockRangeExceedsHead { requested, head }
1078 }
1079 }
1080 }
1081}
1082
1083struct ReceiptBlockResult<P>
1086where
1087 P: BlockReader,
1088{
1089 receipts: Arc<Vec<ProviderReceipt<P>>>,
1091 recovered_block: Option<Arc<reth_primitives_traits::RecoveredBlock<ProviderBlock<P>>>>,
1093 header: SealedHeader<<P as HeaderProvider>::Header>,
1095}
1096
1097enum RangeMode<
1099 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1100> {
1101 Cached(CachedMode<Eth>),
1103 Range(RangeBlockMode<Eth>),
1105}
1106
1107impl<
1108 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1109 > RangeMode<Eth>
1110{
1111 fn new(
1113 filter_inner: Arc<EthFilterInner<Eth>>,
1114 sealed_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1115 from_block: u64,
1116 to_block: u64,
1117 max_headers_range: u64,
1118 chain_tip: u64,
1119 ) -> Self {
1120 let block_count = to_block - from_block + 1;
1121 let distance_from_tip = chain_tip.saturating_sub(to_block);
1122
1123 let use_cached_mode =
1125 Self::should_use_cached_mode(&sealed_headers, block_count, distance_from_tip);
1126
1127 let parallel = sealed_headers.len() >= PARALLEL_PROCESSING_THRESHOLD;
1130
1131 if use_cached_mode && !sealed_headers.is_empty() {
1132 Self::Cached(CachedMode { filter_inner, headers_iter: sealed_headers.into_iter() })
1133 } else {
1134 Self::Range(RangeBlockMode {
1135 filter_inner,
1136 iter: sealed_headers.into_iter().peekable(),
1137 next: VecDeque::new(),
1138 max_range: (max_headers_range as usize).min(MAX_PARALLEL_BATCH_SIZE),
1139 parallel,
1140 pending_tasks: FuturesOrdered::new(),
1141 })
1142 }
1143 }
1144
1145 const fn should_use_cached_mode(
1147 headers: &[SealedHeader<<Eth::Provider as HeaderProvider>::Header>],
1148 block_count: u64,
1149 distance_from_tip: u64,
1150 ) -> bool {
1151 let bloom_matches = headers.len();
1153
1154 let adjusted_threshold = Self::calculate_adjusted_threshold(block_count, bloom_matches);
1156
1157 block_count <= adjusted_threshold && distance_from_tip <= adjusted_threshold
1158 }
1159
1160 const fn calculate_adjusted_threshold(block_count: u64, bloom_matches: usize) -> u64 {
1162 if block_count <= BLOOM_ADJUSTMENT_MIN_BLOCKS {
1164 return CACHED_MODE_BLOCK_THRESHOLD;
1165 }
1166
1167 match bloom_matches {
1168 n if n > HIGH_BLOOM_MATCH_THRESHOLD => CACHED_MODE_BLOCK_THRESHOLD / 2,
1169 n if n > MODERATE_BLOOM_MATCH_THRESHOLD => (CACHED_MODE_BLOCK_THRESHOLD * 3) / 4,
1170 _ => CACHED_MODE_BLOCK_THRESHOLD,
1171 }
1172 }
1173
1174 async fn next(&mut self) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1176 match self {
1177 Self::Cached(cached) => cached.next().await,
1178 Self::Range(range) => range.next().await,
1179 }
1180 }
1181}
1182
1183struct CachedMode<
1185 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1186> {
1187 filter_inner: Arc<EthFilterInner<Eth>>,
1188 headers_iter: std::vec::IntoIter<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1189}
1190
1191impl<
1192 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1193 > CachedMode<Eth>
1194{
1195 async fn next(&mut self) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1196 let Some(header) = self.headers_iter.next() else { return Ok(None) };
1197
1198 let Some((receipts, maybe_block)) =
1200 self.filter_inner.eth_cache().get_receipts_and_maybe_block(header.hash()).await?
1201 else {
1202 return Err(EthFilterError::ReceiptsUnavailable(header.number()))
1203 };
1204
1205 Ok(Some(ReceiptBlockResult { receipts, recovered_block: maybe_block, header }))
1206 }
1207}
1208
1209type ReceiptFetchFuture<P> =
1211 Pin<Box<dyn Future<Output = Result<Vec<ReceiptBlockResult<P>>, EthFilterError>> + Send>>;
1212
1213struct RangeBlockMode<
1215 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1216> {
1217 filter_inner: Arc<EthFilterInner<Eth>>,
1218 iter: Peekable<std::vec::IntoIter<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>>,
1219 next: VecDeque<ReceiptBlockResult<Eth::Provider>>,
1220 max_range: usize,
1222 parallel: bool,
1224 pending_tasks: FuturesOrdered<ReceiptFetchFuture<Eth::Provider>>,
1226}
1227
1228impl<
1229 Eth: RpcNodeCoreExt<Provider: BlockIdReader, Pool: TransactionPool> + LoadReceipt + EthBlocks,
1230 > RangeBlockMode<Eth>
1231{
1232 async fn next(&mut self) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1233 loop {
1234 if let Some(result) = self.next.pop_front() {
1236 return Ok(Some(result));
1237 }
1238
1239 if let Some(task_result) = self.pending_tasks.next().await {
1241 self.next.extend(task_result?);
1242 continue;
1243 }
1244
1245 let Some(next_header) = self.iter.next() else {
1247 return Ok(None);
1249 };
1250
1251 if !self.parallel {
1252 if let Some(result) = self.process_small_range(vec![next_header]).await? {
1254 return Ok(Some(result));
1255 }
1256 continue;
1258 }
1259
1260 let mut range_headers = Vec::with_capacity(self.max_range.min(self.iter.len() + 1));
1261 range_headers.push(next_header);
1262
1263 while range_headers.len() < self.max_range {
1265 let Some(peeked) = self.iter.peek() else { break };
1266 let Some(last_header) = range_headers.last() else { break };
1267
1268 let expected_next = last_header.number() + 1;
1269 if peeked.number() != expected_next {
1270 trace!(
1271 target: "rpc::eth::filter",
1272 last_block = last_header.number(),
1273 next_block = peeked.number(),
1274 expected = expected_next,
1275 range_size = range_headers.len(),
1276 "Non-consecutive block detected, stopping range collection"
1277 );
1278 break; }
1280
1281 let Some(next_header) = self.iter.next() else { break };
1282 range_headers.push(next_header);
1283 }
1284
1285 self.spawn_parallel_tasks(range_headers);
1286 }
1288 }
1289
1290 async fn process_small_range(
1294 &mut self,
1295 range_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1296 ) -> Result<Option<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1297 for header in range_headers {
1299 let (maybe_block, maybe_receipts) = self
1301 .filter_inner
1302 .eth_cache()
1303 .maybe_cached_block_and_receipts(header.hash())
1304 .await?;
1305
1306 let receipts = match maybe_receipts {
1307 Some(receipts) => receipts,
1308 None => {
1309 match self.filter_inner.provider().receipts_by_block(header.hash().into())? {
1311 Some(receipts) => Arc::new(receipts),
1312 None => return Err(EthFilterError::ReceiptsUnavailable(header.number())),
1313 }
1314 }
1315 };
1316
1317 if !receipts.is_empty() {
1318 self.next.push_back(ReceiptBlockResult {
1319 receipts,
1320 recovered_block: maybe_block,
1321 header,
1322 });
1323 }
1324 }
1325
1326 Ok(self.next.pop_front())
1327 }
1328
1329 fn spawn_parallel_tasks(
1333 &mut self,
1334 range_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1335 ) {
1336 let chunk_size = range_headers.len().div_ceil(DEFAULT_PARALLEL_CONCURRENCY).max(1);
1338 let header_chunks = range_headers
1339 .into_iter()
1340 .chunks(chunk_size)
1341 .into_iter()
1342 .map(|chunk| chunk.collect::<Vec<_>>())
1343 .collect::<Vec<_>>();
1344
1345 for chunk_headers in header_chunks {
1347 let filter_inner = self.filter_inner.clone();
1348 let fetch = move || Self::fetch_chunk_receipts(&filter_inner, chunk_headers);
1349
1350 let chunk_task: ReceiptFetchFuture<Eth::Provider> = match self
1353 .filter_inner
1354 .eth_api
1355 .blocking_io_task_guard()
1356 .clone()
1357 .try_acquire_owned()
1358 {
1359 Ok(permit) => Box::pin(async move {
1360 let chunk_task = reth_tasks::spawn_blocking(move || {
1361 let _permit = permit;
1362 fetch()
1363 });
1364
1365 match chunk_task.await {
1367 Ok(chunk_results) => chunk_results,
1368 Err(join_err) => {
1369 trace!(target: "rpc::eth::filter", error = ?join_err, "Task join error");
1370 Err(EthFilterError::InternalError)
1371 }
1372 }
1373 }),
1374 Err(_) => Box::pin(async move { fetch() }),
1375 };
1376
1377 self.pending_tasks.push_back(chunk_task);
1378 }
1379 }
1380
1381 fn fetch_chunk_receipts(
1383 filter_inner: &EthFilterInner<Eth>,
1384 chunk_headers: Vec<SealedHeader<<Eth::Provider as HeaderProvider>::Header>>,
1385 ) -> Result<Vec<ReceiptBlockResult<Eth::Provider>>, EthFilterError> {
1386 let mut chunk_results = Vec::with_capacity(chunk_headers.len());
1387
1388 for header in chunk_headers {
1389 let receipts = match filter_inner.provider().receipts_by_block(header.hash().into())? {
1392 Some(receipts) => Arc::new(receipts),
1393 None => return Err(EthFilterError::ReceiptsUnavailable(header.number())),
1394 };
1395
1396 if !receipts.is_empty() {
1397 chunk_results.push(ReceiptBlockResult { receipts, recovered_block: None, header });
1398 }
1399 }
1400
1401 Ok(chunk_results)
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use super::*;
1408 use crate::{eth::EthApi, EthApiBuilder};
1409 use alloy_consensus::TxLegacy;
1410 use alloy_network::Ethereum;
1411 use alloy_primitives::{Bloom, FixedBytes};
1412 use rand::Rng;
1413 use reth_chainspec::{ChainSpec, ChainSpecProvider};
1414 use reth_db_api::models::StoredBlockBodyIndices;
1415 use reth_ethereum_primitives::TxType;
1416 use reth_evm_ethereum::EthEvmConfig;
1417 use reth_network_api::noop::NoopNetwork;
1418 use reth_provider::test_utils::MockEthProvider;
1419 use reth_rpc_convert::RpcConverter;
1420 use reth_rpc_eth_api::{helpers::SpawnBlocking, node::RpcNodeCoreAdapter};
1421 use reth_rpc_eth_types::receipt::EthReceiptConverter;
1422 use reth_tasks::Runtime;
1423 use reth_testing_utils::generators;
1424 use reth_transaction_pool::test_utils::{testing_pool, TestPool};
1425 use std::{collections::VecDeque, sync::Arc};
1426
1427 #[test]
1428 fn receipts_unavailable_error_matches_geth() {
1429 let err: jsonrpsee::types::error::ErrorObject<'static> =
1430 EthFilterError::ReceiptsUnavailable(100).into();
1431 assert_eq!(err.code(), 4444);
1432 assert_eq!(err.message(), "pruned history unavailable");
1433 }
1434
1435 #[test]
1436 fn test_block_range_iter() {
1437 let mut rng = generators::rng();
1438
1439 let start = rng.random::<u32>() as u64;
1440 let end = start.saturating_add(rng.random::<u32>() as u64);
1441 let step = rng.random::<u16>() as u64;
1442 let range = start..=end;
1443 let mut iter = BlockRangeInclusiveIter::new(range.clone(), step);
1444 let (from, mut end) = iter.next().unwrap();
1445 assert_eq!(from, start);
1446 assert_eq!(end, (from + step).min(*range.end()));
1447
1448 for (next_from, next_end) in iter {
1449 assert_eq!(next_from, end + 1);
1451 end = next_end;
1452 }
1453
1454 assert_eq!(end, *range.end());
1455 }
1456
1457 #[expect(clippy::type_complexity)]
1459 fn build_test_eth_api(
1460 provider: MockEthProvider,
1461 ) -> EthApi<
1462 RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
1463 RpcConverter<Ethereum, EthEvmConfig, EthReceiptConverter<ChainSpec>>,
1464 > {
1465 EthApiBuilder::new(
1466 provider.clone(),
1467 testing_pool(),
1468 NoopNetwork::default(),
1469 EthEvmConfig::new(provider.chain_spec()),
1470 )
1471 .build()
1472 }
1473
1474 #[tokio::test]
1475 async fn test_logs_for_filter_from_block_beyond_head() {
1476 let provider = MockEthProvider::default();
1477 provider.add_header(FixedBytes::random(), alloy_consensus::Header::default());
1478 let eth_api = build_test_eth_api(provider);
1479
1480 let eth_filter =
1481 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1482
1483 let filter = Filter::new().from_block(100u64).to_block(BlockNumberOrTag::Latest);
1484 let result = eth_filter.inner.clone().logs_for_filter(filter, QueryLimits::default()).await;
1485 assert!(matches!(result, Err(EthFilterError::InvalidBlockRangeParams)), "{result:?}");
1486 }
1487
1488 #[tokio::test]
1489 async fn test_range_block_mode_empty_range() {
1490 let provider = MockEthProvider::default();
1491 let eth_api = build_test_eth_api(provider);
1492
1493 let eth_filter =
1494 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1495 let filter_inner = eth_filter.inner;
1496
1497 let headers = vec![];
1498 let max_range = 100;
1499
1500 let mut range_mode = RangeBlockMode {
1501 filter_inner,
1502 iter: headers.into_iter().peekable(),
1503 next: VecDeque::new(),
1504 max_range,
1505 parallel: false,
1506
1507 pending_tasks: FuturesOrdered::new(),
1508 };
1509
1510 let result = range_mode.next().await;
1511 assert!(result.is_ok());
1512 assert!(result.unwrap().is_none());
1513 }
1514
1515 #[tokio::test]
1516 async fn test_range_block_mode_queued_results_priority() {
1517 let provider = MockEthProvider::default();
1518
1519 let headers = vec![
1520 SealedHeader::new(
1521 alloy_consensus::Header { number: 100, ..Default::default() },
1522 FixedBytes::random(),
1523 ),
1524 SealedHeader::new(
1525 alloy_consensus::Header { number: 101, ..Default::default() },
1526 FixedBytes::random(),
1527 ),
1528 ];
1529 for header in &headers {
1530 provider.add_header(header.hash(), header.header().clone());
1531 provider.add_receipts(header.number(), vec![]);
1532 }
1533
1534 let eth_api = build_test_eth_api(provider);
1535
1536 let eth_filter =
1537 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1538 let filter_inner = eth_filter.inner;
1539
1540 let expected_block_hash_1 = FixedBytes::from([1u8; 32]);
1542 let expected_block_hash_2 = FixedBytes::from([2u8; 32]);
1543
1544 let mock_receipt_1 = reth_ethereum_primitives::Receipt {
1546 tx_type: TxType::Legacy,
1547 cumulative_gas_used: 100_000,
1548 logs: vec![],
1549 success: true,
1550 };
1551 let mock_receipt_2 = reth_ethereum_primitives::Receipt {
1552 tx_type: TxType::Eip1559,
1553 cumulative_gas_used: 200_000,
1554 logs: vec![],
1555 success: true,
1556 };
1557 let mock_receipt_3 = reth_ethereum_primitives::Receipt {
1558 tx_type: TxType::Eip2930,
1559 cumulative_gas_used: 150_000,
1560 logs: vec![],
1561 success: false, };
1563
1564 let mock_result_1 = ReceiptBlockResult {
1565 receipts: Arc::new(vec![mock_receipt_1.clone(), mock_receipt_2.clone()]),
1566 recovered_block: None,
1567 header: SealedHeader::new(
1568 alloy_consensus::Header { number: 42, ..Default::default() },
1569 expected_block_hash_1,
1570 ),
1571 };
1572
1573 let mock_result_2 = ReceiptBlockResult {
1574 receipts: Arc::new(vec![mock_receipt_3.clone()]),
1575 recovered_block: None,
1576 header: SealedHeader::new(
1577 alloy_consensus::Header { number: 43, ..Default::default() },
1578 expected_block_hash_2,
1579 ),
1580 };
1581
1582 let mut range_mode = RangeBlockMode {
1583 filter_inner,
1584 iter: headers.into_iter().peekable(),
1585 next: VecDeque::from([mock_result_1, mock_result_2]), max_range: 100,
1587 parallel: false,
1588
1589 pending_tasks: FuturesOrdered::new(),
1590 };
1591
1592 let result1 = range_mode.next().await;
1594 assert!(result1.is_ok());
1595 let receipt_result1 = result1.unwrap().unwrap();
1596 assert_eq!(receipt_result1.header.hash(), expected_block_hash_1);
1597 assert_eq!(receipt_result1.header.number, 42);
1598
1599 assert_eq!(receipt_result1.receipts.len(), 2);
1601 assert_eq!(receipt_result1.receipts[0].tx_type, mock_receipt_1.tx_type);
1602 assert_eq!(
1603 receipt_result1.receipts[0].cumulative_gas_used,
1604 mock_receipt_1.cumulative_gas_used
1605 );
1606 assert_eq!(receipt_result1.receipts[0].success, mock_receipt_1.success);
1607 assert_eq!(receipt_result1.receipts[1].tx_type, mock_receipt_2.tx_type);
1608 assert_eq!(
1609 receipt_result1.receipts[1].cumulative_gas_used,
1610 mock_receipt_2.cumulative_gas_used
1611 );
1612 assert_eq!(receipt_result1.receipts[1].success, mock_receipt_2.success);
1613
1614 let result2 = range_mode.next().await;
1616 assert!(result2.is_ok());
1617 let receipt_result2 = result2.unwrap().unwrap();
1618 assert_eq!(receipt_result2.header.hash(), expected_block_hash_2);
1619 assert_eq!(receipt_result2.header.number, 43);
1620
1621 assert_eq!(receipt_result2.receipts.len(), 1);
1623 assert_eq!(receipt_result2.receipts[0].tx_type, mock_receipt_3.tx_type);
1624 assert_eq!(
1625 receipt_result2.receipts[0].cumulative_gas_used,
1626 mock_receipt_3.cumulative_gas_used
1627 );
1628 assert_eq!(receipt_result2.receipts[0].success, mock_receipt_3.success);
1629
1630 assert!(range_mode.next.is_empty());
1632
1633 let result3 = range_mode.next().await;
1634 assert!(result3.is_ok());
1635 }
1636
1637 #[tokio::test]
1638 async fn test_range_block_mode_single_block_missing_receipts() {
1639 let provider = MockEthProvider::default();
1640 let eth_api = build_test_eth_api(provider);
1641
1642 let eth_filter =
1643 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1644 let filter_inner = eth_filter.inner;
1645
1646 let headers = vec![SealedHeader::new(
1647 alloy_consensus::Header { number: 100, ..Default::default() },
1648 FixedBytes::random(),
1649 )];
1650
1651 let mut range_mode = RangeBlockMode {
1652 filter_inner,
1653 iter: headers.into_iter().peekable(),
1654 next: VecDeque::new(),
1655 max_range: 100,
1656 parallel: false,
1657
1658 pending_tasks: FuturesOrdered::new(),
1659 };
1660
1661 let Err(err) = range_mode.next().await else { panic!("missing receipts must be an error") };
1664 assert!(matches!(err, EthFilterError::ReceiptsUnavailable(100)), "{err:?}");
1665 }
1666
1667 #[tokio::test]
1668 async fn test_range_block_mode_provider_receipts() {
1669 let provider = MockEthProvider::default();
1670
1671 let header_1 = alloy_consensus::Header { number: 100, ..Default::default() };
1672 let header_2 = alloy_consensus::Header { number: 101, ..Default::default() };
1673 let header_3 = alloy_consensus::Header { number: 102, ..Default::default() };
1674
1675 let block_hash_1 = FixedBytes::random();
1676 let block_hash_2 = FixedBytes::random();
1677 let block_hash_3 = FixedBytes::random();
1678
1679 provider.add_header(block_hash_1, header_1.clone());
1680 provider.add_header(block_hash_2, header_2.clone());
1681 provider.add_header(block_hash_3, header_3.clone());
1682
1683 let mock_log = alloy_primitives::Log {
1685 address: alloy_primitives::Address::ZERO,
1686 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
1687 };
1688
1689 let receipt_100_1 = reth_ethereum_primitives::Receipt {
1690 tx_type: TxType::Legacy,
1691 cumulative_gas_used: 21_000,
1692 logs: vec![mock_log.clone()],
1693 success: true,
1694 };
1695 let receipt_100_2 = reth_ethereum_primitives::Receipt {
1696 tx_type: TxType::Eip1559,
1697 cumulative_gas_used: 42_000,
1698 logs: vec![mock_log.clone()],
1699 success: true,
1700 };
1701 let receipt_101_1 = reth_ethereum_primitives::Receipt {
1702 tx_type: TxType::Eip2930,
1703 cumulative_gas_used: 30_000,
1704 logs: vec![mock_log.clone()],
1705 success: false,
1706 };
1707
1708 provider.add_receipts(100, vec![receipt_100_1.clone(), receipt_100_2.clone()]);
1709 provider.add_receipts(101, vec![receipt_101_1.clone()]);
1710 provider.add_receipts(102, vec![]);
1712
1713 let eth_api = build_test_eth_api(provider);
1714
1715 let eth_filter =
1716 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1717 let filter_inner = eth_filter.inner;
1718
1719 let headers = vec![
1720 SealedHeader::new(header_1, block_hash_1),
1721 SealedHeader::new(header_2, block_hash_2),
1722 SealedHeader::new(header_3, block_hash_3),
1723 ];
1724
1725 let mut range_mode = RangeBlockMode {
1726 filter_inner,
1727 iter: headers.into_iter().peekable(),
1728 next: VecDeque::new(),
1729 max_range: 3, parallel: false,
1731
1732 pending_tasks: FuturesOrdered::new(),
1733 };
1734
1735 let result = range_mode.next().await;
1737 assert!(result.is_ok());
1738 let receipt_result = result.unwrap().unwrap();
1739
1740 assert_eq!(receipt_result.header.hash(), block_hash_1);
1741 assert_eq!(receipt_result.header.number, 100);
1742 assert_eq!(receipt_result.receipts.len(), 2);
1743
1744 assert_eq!(receipt_result.receipts[0].tx_type, receipt_100_1.tx_type);
1746 assert_eq!(
1747 receipt_result.receipts[0].cumulative_gas_used,
1748 receipt_100_1.cumulative_gas_used
1749 );
1750 assert_eq!(receipt_result.receipts[0].success, receipt_100_1.success);
1751
1752 assert_eq!(receipt_result.receipts[1].tx_type, receipt_100_2.tx_type);
1753 assert_eq!(
1754 receipt_result.receipts[1].cumulative_gas_used,
1755 receipt_100_2.cumulative_gas_used
1756 );
1757 assert_eq!(receipt_result.receipts[1].success, receipt_100_2.success);
1758
1759 let result2 = range_mode.next().await;
1761 assert!(result2.is_ok());
1762 let receipt_result2 = result2.unwrap().unwrap();
1763
1764 assert_eq!(receipt_result2.header.hash(), block_hash_2);
1765 assert_eq!(receipt_result2.header.number, 101);
1766 assert_eq!(receipt_result2.receipts.len(), 1);
1767
1768 assert_eq!(receipt_result2.receipts[0].tx_type, receipt_101_1.tx_type);
1770 assert_eq!(
1771 receipt_result2.receipts[0].cumulative_gas_used,
1772 receipt_101_1.cumulative_gas_used
1773 );
1774 assert_eq!(receipt_result2.receipts[0].success, receipt_101_1.success);
1775
1776 let result3 = range_mode.next().await;
1778 assert!(result3.is_ok());
1779 assert!(result3.unwrap().is_none());
1780 }
1781
1782 #[tokio::test]
1783 async fn test_range_block_mode_iterator_exhaustion() {
1784 let provider = MockEthProvider::default();
1785
1786 let header_100 = alloy_consensus::Header { number: 100, ..Default::default() };
1787 let header_101 = alloy_consensus::Header { number: 101, ..Default::default() };
1788
1789 let block_hash_100 = FixedBytes::random();
1790 let block_hash_101 = FixedBytes::random();
1791
1792 provider.add_header(block_hash_100, header_100.clone());
1794 provider.add_header(block_hash_101, header_101.clone());
1795
1796 let mock_receipt = reth_ethereum_primitives::Receipt {
1798 tx_type: TxType::Legacy,
1799 cumulative_gas_used: 21_000,
1800 logs: vec![],
1801 success: true,
1802 };
1803 provider.add_receipts(100, vec![mock_receipt.clone()]);
1804 provider.add_receipts(101, vec![mock_receipt.clone()]);
1805
1806 let eth_api = build_test_eth_api(provider);
1807
1808 let eth_filter =
1809 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1810 let filter_inner = eth_filter.inner;
1811
1812 let headers = vec![
1813 SealedHeader::new(header_100, block_hash_100),
1814 SealedHeader::new(header_101, block_hash_101),
1815 ];
1816
1817 let mut range_mode = RangeBlockMode {
1818 filter_inner,
1819 iter: headers.into_iter().peekable(),
1820 next: VecDeque::new(),
1821 max_range: 1,
1822 parallel: false,
1823
1824 pending_tasks: FuturesOrdered::new(),
1825 };
1826
1827 let result1 = range_mode.next().await;
1828 assert!(result1.is_ok());
1829 assert!(result1.unwrap().is_some()); assert!(range_mode.iter.peek().is_some()); let result2 = range_mode.next().await;
1834 assert!(result2.is_ok());
1835 assert!(result2.unwrap().is_some()); assert!(range_mode.iter.peek().is_none());
1839
1840 let result3 = range_mode.next().await;
1842 assert!(result3.is_ok());
1843 assert!(result3.unwrap().is_none());
1844 }
1845
1846 #[tokio::test]
1847 async fn test_cached_mode_with_mock_receipts() {
1848 let test_hash = FixedBytes::from([42u8; 32]);
1850 let test_block_number = 100u64;
1851 let test_header = SealedHeader::new(
1852 alloy_consensus::Header {
1853 number: test_block_number,
1854 gas_used: 50_000,
1855 ..Default::default()
1856 },
1857 test_hash,
1858 );
1859
1860 let mock_log = alloy_primitives::Log {
1862 address: alloy_primitives::Address::ZERO,
1863 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
1864 };
1865
1866 let mock_receipt = reth_ethereum_primitives::Receipt {
1867 tx_type: TxType::Legacy,
1868 cumulative_gas_used: 21_000,
1869 logs: vec![mock_log],
1870 success: true,
1871 };
1872
1873 let provider = MockEthProvider::default();
1874 provider.add_header(test_hash, test_header.header().clone());
1875 provider.add_receipts(test_block_number, vec![mock_receipt.clone()]);
1876
1877 let eth_api = build_test_eth_api(provider);
1878 let eth_filter =
1879 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1880 let filter_inner = eth_filter.inner;
1881
1882 let headers = vec![test_header.clone()];
1883
1884 let mut cached_mode = CachedMode { filter_inner, headers_iter: headers.into_iter() };
1885
1886 let result = cached_mode.next().await.expect("next should succeed");
1888 let receipt_block_result = result.expect("should have receipt result");
1889 assert_eq!(receipt_block_result.header.hash(), test_hash);
1890 assert_eq!(receipt_block_result.header.number, test_block_number);
1891 assert_eq!(receipt_block_result.receipts.len(), 1);
1892 assert_eq!(receipt_block_result.receipts[0].tx_type, mock_receipt.tx_type);
1893 assert_eq!(
1894 receipt_block_result.receipts[0].cumulative_gas_used,
1895 mock_receipt.cumulative_gas_used
1896 );
1897 assert_eq!(receipt_block_result.receipts[0].success, mock_receipt.success);
1898
1899 let result2 = cached_mode.next().await;
1901 assert!(result2.is_ok());
1902 assert!(result2.unwrap().is_none());
1903 }
1904
1905 #[tokio::test]
1906 async fn test_cached_mode_empty_headers() {
1907 let provider = MockEthProvider::default();
1908 let eth_api = build_test_eth_api(provider);
1909
1910 let eth_filter =
1911 super::EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1912 let filter_inner = eth_filter.inner;
1913
1914 let headers: Vec<SealedHeader<alloy_consensus::Header>> = vec![];
1915
1916 let mut cached_mode = CachedMode { filter_inner, headers_iter: headers.into_iter() };
1917
1918 let result = cached_mode.next().await.expect("next should succeed");
1920 assert!(result.is_none());
1921 }
1922
1923 #[tokio::test]
1924 async fn test_log_limit_retry_range_excludes_overflow_block() {
1925 let provider = MockEthProvider::default();
1926
1927 use reth_ethereum_primitives::{TransactionSigned, TxType};
1928
1929 let tx_inner = TxLegacy {
1930 chain_id: Some(1),
1931 nonce: 0,
1932 gas_price: 21_000,
1933 gas_limit: 21_000,
1934 to: alloy_primitives::TxKind::Call(alloy_primitives::Address::ZERO),
1935 value: alloy_primitives::U256::ZERO,
1936 input: alloy_primitives::Bytes::new(),
1937 };
1938 let signature = alloy_primitives::Signature::test_signature();
1939 let tx = TransactionSigned::new_unhashed(tx_inner.into(), signature);
1940
1941 let mock_log = alloy_primitives::Log {
1942 address: alloy_primitives::Address::ZERO,
1943 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
1944 };
1945
1946 let receipt = reth_ethereum_primitives::Receipt {
1947 tx_type: TxType::Legacy,
1948 cumulative_gas_used: 21_000,
1949 logs: vec![mock_log],
1950 success: true,
1951 };
1952
1953 let mut prev_hash = alloy_primitives::B256::default();
1954 for (idx, block_number) in (100u64..=102).enumerate() {
1955 let header = alloy_consensus::Header {
1956 number: block_number,
1957 parent_hash: prev_hash,
1958 logs_bloom: Bloom::repeat_byte(1u8),
1959 ..Default::default()
1960 };
1961 let hash = header.hash_slow();
1962 prev_hash = hash;
1963
1964 let block = reth_ethereum_primitives::Block {
1965 header,
1966 body: reth_ethereum_primitives::BlockBody {
1967 transactions: vec![tx.clone()],
1968 ..Default::default()
1969 },
1970 };
1971 provider.add_block(hash, block);
1972 provider.add_receipts(block_number, vec![receipt.clone()]);
1973 provider.add_block_body_indices(
1974 block_number,
1975 StoredBlockBodyIndices { first_tx_num: idx as u64, tx_count: 1 },
1976 );
1977 }
1978
1979 let eth_api = build_test_eth_api(provider);
1980 let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
1981 let err = eth_filter
1982 .inner
1983 .clone()
1984 .get_logs_in_block_range(
1985 Filter::default(),
1986 100,
1987 102,
1988 QueryLimits { max_blocks_per_filter: None, max_logs_per_response: Some(2) },
1989 )
1990 .await
1991 .expect_err("range should exceed max logs");
1992
1993 let EthFilterError::QueryExceedsMaxResults { max_logs, from_block, to_block } = err else {
1994 panic!("unexpected error: {err:?}");
1995 };
1996
1997 assert_eq!(max_logs, 2);
1998 assert_eq!(from_block, 100);
1999 assert_eq!(to_block, 101);
2000 }
2001
2002 #[tokio::test]
2003 async fn test_non_consecutive_headers_after_bloom_filter() {
2004 let provider = MockEthProvider::default();
2005
2006 let mut expected_hashes = vec![];
2008 let mut prev_hash = alloy_primitives::B256::default();
2009
2010 use reth_ethereum_primitives::{TransactionSigned, TxType};
2012
2013 let tx_inner = TxLegacy {
2014 chain_id: Some(1),
2015 nonce: 0,
2016 gas_price: 21_000,
2017 gas_limit: 21_000,
2018 to: alloy_primitives::TxKind::Call(alloy_primitives::Address::ZERO),
2019 value: alloy_primitives::U256::ZERO,
2020 input: alloy_primitives::Bytes::new(),
2021 };
2022 let signature = alloy_primitives::Signature::test_signature();
2023 let tx = TransactionSigned::new_unhashed(tx_inner.into(), signature);
2024
2025 for i in 100u64..=103 {
2026 let header = alloy_consensus::Header {
2027 number: i,
2028 parent_hash: prev_hash,
2029 logs_bloom: if i == 100 || i == 102 {
2031 Bloom::repeat_byte(1u8)
2032 } else {
2033 alloy_primitives::Bloom::default()
2034 },
2035 ..Default::default()
2036 };
2037
2038 let hash = header.hash_slow();
2039 expected_hashes.push(hash);
2040 prev_hash = hash;
2041
2042 let transactions = if i == 100 || i == 102 { vec![tx.clone()] } else { vec![] };
2044
2045 let block = reth_ethereum_primitives::Block {
2046 header,
2047 body: reth_ethereum_primitives::BlockBody { transactions, ..Default::default() },
2048 };
2049 provider.add_block(hash, block);
2050 }
2051
2052 let mock_log = alloy_primitives::Log {
2054 address: alloy_primitives::Address::ZERO,
2055 data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
2056 };
2057
2058 let receipt = reth_ethereum_primitives::Receipt {
2059 tx_type: TxType::Legacy,
2060 cumulative_gas_used: 21_000,
2061 logs: vec![mock_log],
2062 success: true,
2063 };
2064
2065 provider.add_receipts(100, vec![receipt.clone()]);
2066 provider.add_receipts(101, vec![]);
2067 provider.add_receipts(102, vec![receipt.clone()]);
2068 provider.add_receipts(103, vec![]);
2069
2070 provider
2072 .add_block_body_indices(100, StoredBlockBodyIndices { first_tx_num: 0, tx_count: 1 });
2073 provider
2074 .add_block_body_indices(101, StoredBlockBodyIndices { first_tx_num: 1, tx_count: 0 });
2075 provider
2076 .add_block_body_indices(102, StoredBlockBodyIndices { first_tx_num: 1, tx_count: 1 });
2077 provider
2078 .add_block_body_indices(103, StoredBlockBodyIndices { first_tx_num: 2, tx_count: 0 });
2079
2080 let eth_api = build_test_eth_api(provider);
2081 let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
2082
2083 let filter = Filter::default();
2085
2086 let logs = eth_filter
2088 .inner
2089 .clone()
2090 .get_logs_in_block_range(filter, 100, 103, QueryLimits::default())
2091 .await
2092 .expect("should succeed");
2093
2094 assert_eq!(logs.len(), 2);
2096
2097 assert_eq!(logs[0].block_number, Some(100));
2098 assert_eq!(logs[1].block_number, Some(102));
2099
2100 assert_eq!(logs[0].block_hash, Some(expected_hashes[0])); assert_eq!(logs[1].block_hash, Some(expected_hashes[2])); }
2104
2105 #[tokio::test]
2106 async fn test_range_scan_waits_for_blocking_io_permit() {
2107 let provider = MockEthProvider::default();
2108 let header = alloy_consensus::Header::default();
2109 provider.add_header(header.hash_slow(), header);
2110 provider.add_receipts(0, vec![]);
2111 let eth_api = build_test_eth_api(provider);
2112
2113 let guard = eth_api.blocking_io_task_guard().clone();
2115 let permits =
2116 guard.clone().acquire_many_owned(guard.available_permits() as u32).await.unwrap();
2117
2118 let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
2119 let scan = eth_filter.inner.clone().get_logs_in_block_range(
2120 Filter::default(),
2121 0,
2122 0,
2123 QueryLimits::default(),
2124 );
2125 tokio::pin!(scan);
2126 assert!(
2127 tokio::time::timeout(Duration::from_millis(100), &mut scan).await.is_err(),
2128 "scan must wait for a blocking IO permit"
2129 );
2130
2131 drop(permits);
2132 assert!(scan.await.unwrap().is_empty());
2133 }
2134
2135 #[tokio::test]
2136 async fn test_range_scan_across_windows() {
2137 use alloy_primitives::{Address, Bloom, Bytes, Log, LogData, Signature};
2138 use reth_ethereum_primitives::{Block, BlockBody, Receipt, TransactionSigned};
2139
2140 let provider = MockEthProvider::default();
2141 let tx = TransactionSigned::new_unhashed(
2142 TxLegacy {
2143 chain_id: Some(1),
2144 gas_price: 21_000,
2145 gas_limit: 21_000,
2146 ..Default::default()
2147 }
2148 .into(),
2149 Signature::test_signature(),
2150 );
2151 let receipt = Receipt {
2152 tx_type: TxType::Legacy,
2153 cumulative_gas_used: 21_000,
2154 logs: vec![Log {
2155 address: Address::ZERO,
2156 data: LogData::new_unchecked(vec![], Bytes::new()),
2157 }],
2158 success: true,
2159 };
2160
2161 let matching_blocks = [5u64, 1_200, 2_400];
2164 let mut parent_hash = FixedBytes::default();
2165 for number in 0..=2_500u64 {
2166 let matches = matching_blocks.contains(&number);
2167 let header = alloy_consensus::Header {
2168 number,
2169 parent_hash,
2170 logs_bloom: if matches { Bloom::repeat_byte(1u8) } else { Bloom::default() },
2171 ..Default::default()
2172 };
2173 parent_hash = header.hash_slow();
2174 let transactions = if matches { vec![tx.clone()] } else { Vec::new() };
2175 provider.add_block(
2176 parent_hash,
2177 Block { header, body: BlockBody { transactions, ..Default::default() } },
2178 );
2179 if matches {
2180 let tx_num = matching_blocks.iter().position(|b| *b == number).unwrap() as u64;
2181 provider.add_receipts(number, vec![receipt.clone()]);
2182 provider.add_block_body_indices(
2183 number,
2184 StoredBlockBodyIndices { first_tx_num: tx_num, tx_count: 1 },
2185 );
2186 } else {
2187 provider.add_receipts(number, vec![]);
2188 }
2189 }
2190
2191 let eth_filter = EthFilter::new(
2192 build_test_eth_api(provider),
2193 EthFilterConfig::default(),
2194 Runtime::test(),
2195 );
2196 let logs = eth_filter
2197 .inner
2198 .clone()
2199 .get_logs_in_block_range(Filter::default(), 0, 2_500, QueryLimits::default())
2200 .await
2201 .unwrap();
2202 let blocks = logs.iter().map(|log| log.block_number.unwrap()).collect::<Vec<_>>();
2203 assert_eq!(blocks, matching_blocks);
2204
2205 let err = eth_filter
2208 .inner
2209 .clone()
2210 .get_logs_in_block_range(
2211 Filter::default(),
2212 0,
2213 2_500,
2214 QueryLimits { max_blocks_per_filter: None, max_logs_per_response: Some(1) },
2215 )
2216 .await
2217 .unwrap_err();
2218 assert!(
2219 matches!(
2220 err,
2221 EthFilterError::QueryExceedsMaxResults {
2222 max_logs: 1,
2223 from_block: 0,
2224 to_block: 1_199
2225 }
2226 ),
2227 "{err:?}"
2228 );
2229 }
2230
2231 #[tokio::test]
2232 async fn test_logs_for_filter_pending_to_block_ends_at_head() {
2233 let provider = MockEthProvider::default();
2234 let header = alloy_consensus::Header { number: 2, ..Default::default() };
2235 let hash = header.hash_slow();
2236 provider.add_header(hash, header);
2237 provider.add_receipts(2, vec![]);
2238 provider.set_pending_block_num_hash(Some(alloy_eips::BlockNumHash::new(3, hash)));
2240
2241 let eth_filter = EthFilter::new(
2242 build_test_eth_api(provider),
2243 EthFilterConfig::default(),
2244 Runtime::test(),
2245 );
2246 for filter in [
2247 Filter::new().from_block(0u64).to_block(BlockNumberOrTag::Pending),
2248 Filter::new().select(BlockNumberOrTag::Pending..),
2249 ] {
2250 let logs = eth_filter
2251 .inner
2252 .clone()
2253 .logs_for_filter(filter, QueryLimits::default())
2254 .await
2255 .unwrap();
2256 assert!(logs.is_empty());
2257 }
2258 }
2259
2260 #[tokio::test]
2261 async fn test_logs_for_filter_over_pruned_receipts() {
2262 let provider = MockEthProvider::default();
2263 let mut parent_hash = FixedBytes::default();
2264 for number in 0..=2u64 {
2265 let header = alloy_consensus::Header {
2266 number,
2267 parent_hash,
2268 logs_bloom: Bloom::repeat_byte(1u8),
2269 ..Default::default()
2270 };
2271 parent_hash = header.hash_slow();
2272 provider.add_block(
2273 parent_hash,
2274 reth_ethereum_primitives::Block { header, body: Default::default() },
2275 );
2276 if number != 1 {
2278 provider.add_receipts(number, vec![]);
2279 }
2280 }
2281
2282 let eth_filter = EthFilter::new(
2283 build_test_eth_api(provider),
2284 EthFilterConfig::default(),
2285 Runtime::test(),
2286 );
2287
2288 let err = eth_filter
2290 .inner
2291 .clone()
2292 .logs_for_filter(Filter::new().from_block(0u64).to_block(2u64), QueryLimits::default())
2293 .await
2294 .unwrap_err();
2295 assert!(matches!(err, EthFilterError::ReceiptsUnavailable(1)), "{err:?}");
2296
2297 let logs = eth_filter
2299 .inner
2300 .clone()
2301 .logs_for_filter(Filter::new().from_block(2u64).to_block(2u64), QueryLimits::default())
2302 .await
2303 .unwrap();
2304 assert!(logs.is_empty());
2305 }
2306}