Skip to main content

reth_rpc_eth_types/cache/
mod.rs

1//! Async caching support for eth RPC
2
3use super::{EthStateCacheConfig, MultiConsumerLruCache};
4use crate::block::CachedTransaction;
5use alloy_consensus::transaction::TxHashRef;
6use alloy_eip7928::bal::DecodedBal;
7use alloy_eips::BlockHashOrNumber;
8use alloy_primitives::{Address, Bytes, TxHash, B256};
9use futures::{Stream, StreamExt};
10use reth_chain_state::CanonStateNotification;
11use reth_errors::{ProviderError, ProviderResult};
12use reth_execution_types::Chain;
13use reth_primitives_traits::{Block, BlockBody, InMemorySize, NodePrimitives, RecoveredBlock};
14use reth_revm::{
15    bytecode::Bytecode,
16    primitives::{StorageKey, StorageValue},
17    state::bal::{
18        AccountBal as RevmAccountBal, AccountInfoBal as RevmAccountInfoBal, Bal as RevmBal,
19        BalWrites as RevmBalWrites, StorageBal as RevmStorageBal,
20    },
21};
22use reth_storage_api::{BalProvider, BlockReader, TransactionVariant};
23use reth_tasks::Runtime;
24use schnellru::{ByLength, Limiter, LruMap};
25use std::{
26    future::Future,
27    pin::Pin,
28    sync::Arc,
29    task::{Context, Poll},
30};
31use tokio::sync::{
32    mpsc::{unbounded_channel, UnboundedSender},
33    oneshot, Semaphore,
34};
35use tokio_stream::wrappers::UnboundedReceiverStream;
36
37pub mod config;
38pub mod db;
39pub mod metrics;
40pub mod multi_consumer;
41
42/// The type that can send the response to a requested [`RecoveredBlock`]
43type BlockWithSendersResponseSender<B> =
44    oneshot::Sender<ProviderResult<Option<Arc<RecoveredBlock<B>>>>>;
45
46/// The type that can send the response to the requested receipts of a block.
47type ReceiptsResponseSender<R> = oneshot::Sender<ProviderResult<Option<Arc<Vec<R>>>>>;
48
49type CachedBlockResponseSender<B> = oneshot::Sender<Option<Arc<RecoveredBlock<B>>>>;
50
51type CachedBalResponseSender = oneshot::Sender<Option<CachedRevmBal>>;
52
53type CachedBlockAndReceiptsResponseSender<B, R> =
54    oneshot::Sender<(Option<Arc<RecoveredBlock<B>>>, Option<Arc<Vec<R>>>)>;
55
56/// The type that can send the response for a transaction hash lookup
57type TransactionHashResponseSender<B, R> = oneshot::Sender<Option<CachedTransaction<B, R>>>;
58
59/// The type that can send the response to a requested revm BAL.
60type BalResponseSender = oneshot::Sender<ProviderResult<Option<CachedRevmBal>>>;
61
62type BlockLruCache<B, L> =
63    MultiConsumerLruCache<B256, Arc<RecoveredBlock<B>>, L, BlockWithSendersResponseSender<B>>;
64
65type ReceiptsLruCache<R, L> =
66    MultiConsumerLruCache<B256, Arc<Vec<R>>, L, ReceiptsResponseSender<R>>;
67
68type BalLruCache<L> = MultiConsumerLruCache<B256, CachedRevmBal, L, BalResponseSender>;
69
70/// Provides async access to cached eth data
71///
72/// This is the frontend for the async caching service which manages cached data on a different
73/// task.
74#[derive(Debug)]
75pub struct EthStateCache<N: NodePrimitives> {
76    to_service: UnboundedSender<CacheAction<N::Block, N::Receipt>>,
77}
78
79impl<N: NodePrimitives> Clone for EthStateCache<N> {
80    fn clone(&self) -> Self {
81        Self { to_service: self.to_service.clone() }
82    }
83}
84
85impl<N: NodePrimitives> EthStateCache<N> {
86    /// Creates and returns both [`EthStateCache`] frontend and the memory bound service.
87    fn create<Provider>(
88        provider: Provider,
89        action_task_spawner: Runtime,
90        config: EthStateCacheConfig,
91    ) -> (Self, EthStateCacheService<Provider, Runtime>)
92    where
93        Provider: BlockReader<Block = N::Block, Receipt = N::Receipt> + BalProvider,
94    {
95        let EthStateCacheConfig {
96            max_blocks,
97            max_receipts,
98            max_bals,
99            max_concurrent_db_requests,
100            max_cached_tx_hashes,
101        } = config;
102        let (to_service, rx) = unbounded_channel();
103
104        let service = EthStateCacheService {
105            provider,
106            full_block_cache: BlockLruCache::new(max_blocks, "blocks"),
107            receipts_cache: ReceiptsLruCache::new(max_receipts, "receipts"),
108            bal_cache: BalLruCache::new(max_bals, "bals"),
109            action_tx: to_service.clone(),
110            action_rx: UnboundedReceiverStream::new(rx),
111            action_task_spawner,
112            rate_limiter: Arc::new(Semaphore::new(max_concurrent_db_requests)),
113            tx_hash_index: LruMap::new(ByLength::new(max_cached_tx_hashes)),
114        };
115        let cache = Self { to_service };
116        (cache, service)
117    }
118
119    /// Creates a new async LRU backed cache service task and spawns it to a new task via the given
120    /// spawner.
121    ///
122    /// The cache is memory limited by the given max bytes values.
123    pub fn spawn_with<Provider>(
124        provider: Provider,
125        config: EthStateCacheConfig,
126        executor: Runtime,
127    ) -> Self
128    where
129        Provider: BlockReader<Block = N::Block, Receipt = N::Receipt>
130            + BalProvider
131            + Clone
132            + Unpin
133            + 'static,
134    {
135        let (this, service) = Self::create(provider, executor.clone(), config);
136        executor.spawn_critical_task("eth state cache", service);
137        this
138    }
139
140    /// Requests the  [`RecoveredBlock`] for the block hash
141    ///
142    /// Returns `None` if the block does not exist.
143    pub async fn get_recovered_block(
144        &self,
145        block_hash: B256,
146    ) -> ProviderResult<Option<Arc<RecoveredBlock<N::Block>>>> {
147        let (response_tx, rx) = oneshot::channel();
148        let _ = self.to_service.send(CacheAction::GetBlockWithSenders { block_hash, response_tx });
149        rx.await.map_err(|_| CacheServiceUnavailable)?
150    }
151
152    /// Requests the block for the given block hash if it is cached.
153    pub async fn get_maybe_block(
154        &self,
155        block_hash: B256,
156    ) -> ProviderResult<Option<Arc<RecoveredBlock<N::Block>>>> {
157        let (response_tx, rx) = oneshot::channel();
158        let _ = self.to_service.send(CacheAction::GetCachedBlock { block_hash, response_tx });
159        rx.await.map_err(|_| CacheServiceUnavailable.into())
160    }
161
162    /// Requests the receipts for the block hash
163    ///
164    /// Returns `None` if the block was not found.
165    pub async fn get_receipts(
166        &self,
167        block_hash: B256,
168    ) -> ProviderResult<Option<Arc<Vec<N::Receipt>>>> {
169        let (response_tx, rx) = oneshot::channel();
170        let _ = self.to_service.send(CacheAction::GetReceipts { block_hash, response_tx });
171        rx.await.map_err(|_| CacheServiceUnavailable)?
172    }
173
174    /// Fetches both receipts and block for the given block hash.
175    pub async fn get_block_and_receipts(
176        &self,
177        block_hash: B256,
178    ) -> ProviderResult<Option<(Arc<RecoveredBlock<N::Block>>, Arc<Vec<N::Receipt>>)>> {
179        let block = self.get_recovered_block(block_hash);
180        let receipts = self.get_receipts(block_hash);
181
182        let (block, receipts) = futures::try_join!(block, receipts)?;
183
184        Ok(block.zip(receipts))
185    }
186
187    /// Retrieves the block and its BAL if the BAL is already cached.
188    ///
189    /// The block is fetched if it is not cached. Returns `None` if the block does not exist.
190    pub async fn get_recovered_block_and_maybe_bal(
191        &self,
192        block_hash: B256,
193    ) -> ProviderResult<
194        Option<(Arc<RecoveredBlock<N::Block>>, Option<Arc<DecodedBal<Arc<RevmBal>>>>)>,
195    > {
196        let (response_tx, rx) = oneshot::channel();
197        let _ = self.to_service.send(CacheAction::GetCachedBal { block_hash, response_tx });
198
199        let block = self.get_recovered_block(block_hash);
200        let (block, bal) = futures::join!(block, rx);
201
202        let bal = bal.map_err(|_| CacheServiceUnavailable)?.map(|cached| cached.0);
203        Ok(block?.map(|block| (block, bal)))
204    }
205
206    /// Retrieves receipts and blocks from cache if block is in the cache, otherwise only receipts.
207    pub async fn get_receipts_and_maybe_block(
208        &self,
209        block_hash: B256,
210    ) -> ProviderResult<Option<(Arc<Vec<N::Receipt>>, Option<Arc<RecoveredBlock<N::Block>>>)>> {
211        let (response_tx, rx) = oneshot::channel();
212        let _ = self.to_service.send(CacheAction::GetCachedBlock { block_hash, response_tx });
213
214        let receipts = self.get_receipts(block_hash);
215
216        let (receipts, block) = futures::join!(receipts, rx);
217
218        let block = block.map_err(|_| CacheServiceUnavailable)?;
219        Ok(receipts?.map(|r| (r, block)))
220    }
221
222    /// Retrieves both block and receipts from cache if available.
223    pub async fn maybe_cached_block_and_receipts(
224        &self,
225        block_hash: B256,
226    ) -> ProviderResult<(Option<Arc<RecoveredBlock<N::Block>>>, Option<Arc<Vec<N::Receipt>>>)> {
227        let (response_tx, rx) = oneshot::channel();
228        let _ = self
229            .to_service
230            .send(CacheAction::GetCachedBlockAndReceipts { block_hash, response_tx });
231        rx.await.map_err(|_| CacheServiceUnavailable.into())
232    }
233
234    /// Looks up a transaction by its hash in the cache index.
235    ///
236    /// Returns the cached block, transaction index, and optionally receipts if the transaction
237    /// is in a cached block.
238    pub async fn get_transaction_by_hash(
239        &self,
240        tx_hash: TxHash,
241    ) -> Option<CachedTransaction<N::Block, N::Receipt>> {
242        let (response_tx, rx) = oneshot::channel();
243        let _ = self.to_service.send(CacheAction::GetTransactionByHash { tx_hash, response_tx });
244        rx.await.ok()?
245    }
246
247    /// Requests the revm BAL for the block hash.
248    ///
249    /// Returns `None` if the BAL does not exist.
250    pub async fn get_bal(
251        &self,
252        block_hash: B256,
253    ) -> ProviderResult<Option<Arc<DecodedBal<Arc<RevmBal>>>>> {
254        let (response_tx, rx) = oneshot::channel();
255        let _ = self.to_service.send(CacheAction::GetBal { block_hash, response_tx });
256        rx.await
257            .map_err(|_| CacheServiceUnavailable)?
258            .map(|maybe_bal| maybe_bal.map(|cached| cached.0))
259    }
260}
261/// Thrown when the cache service task dropped.
262#[derive(Debug, thiserror::Error)]
263#[error("cache service task stopped")]
264pub struct CacheServiceUnavailable;
265
266impl From<CacheServiceUnavailable> for ProviderError {
267    fn from(err: CacheServiceUnavailable) -> Self {
268        Self::other(err)
269    }
270}
271
272/// A task that manages caches for data required by the `eth` rpc implementation.
273///
274/// It provides a caching layer on top of the given
275/// [`StateProvider`](reth_storage_api::StateProvider) and keeps data fetched via the provider in
276/// memory in an LRU cache. If the requested data is missing in the cache it is fetched and inserted
277/// into the cache afterwards. While fetching data from disk is sync, this service is async since
278/// requests and data is shared via channels.
279///
280/// This type is an endless future that listens for incoming messages from the user facing
281/// [`EthStateCache`] via a channel. If the requested data is not cached then it spawns a new task
282/// that does the IO and sends the result back to it. This way the caching service only
283/// handles messages and does LRU lookups and never blocking IO.
284///
285/// Caution: The channel for the data is _unbounded_ it is assumed that this is mainly used by the
286/// `reth_rpc::EthApi` which is typically invoked by the RPC server, which already uses
287/// permits to limit concurrent requests.
288#[must_use = "Type does nothing unless spawned"]
289pub(crate) struct EthStateCacheService<
290    Provider,
291    Tasks,
292    LimitBlocks = ByLength,
293    LimitReceipts = ByLength,
294    LimitBals = ByLength,
295> where
296    Provider: BlockReader + BalProvider,
297    LimitBlocks: Limiter<B256, Arc<RecoveredBlock<Provider::Block>>>,
298    LimitReceipts: Limiter<B256, Arc<Vec<Provider::Receipt>>>,
299    LimitBals: Limiter<B256, CachedRevmBal>,
300{
301    /// The type used to lookup data from disk
302    provider: Provider,
303    /// The LRU cache for full blocks grouped by their block hash.
304    full_block_cache: BlockLruCache<Provider::Block, LimitBlocks>,
305    /// The LRU cache for block receipts grouped by the block hash.
306    receipts_cache: ReceiptsLruCache<Provider::Receipt, LimitReceipts>,
307    /// The LRU cache for revm BALs grouped by the block hash.
308    bal_cache: BalLruCache<LimitBals>,
309    /// Sender half of the action channel.
310    action_tx: UnboundedSender<CacheAction<Provider::Block, Provider::Receipt>>,
311    /// Receiver half of the action channel.
312    action_rx: UnboundedReceiverStream<CacheAction<Provider::Block, Provider::Receipt>>,
313    /// The type that's used to spawn tasks that do the actual work
314    action_task_spawner: Tasks,
315    /// Rate limiter for spawned fetch tasks.
316    ///
317    /// This restricts the max concurrent fetch tasks at the same time.
318    rate_limiter: Arc<Semaphore>,
319    /// LRU index mapping transaction hashes to their block hash and index within the block.
320    tx_hash_index: LruMap<TxHash, (B256, usize), ByLength>,
321}
322
323impl<Provider> EthStateCacheService<Provider, Runtime>
324where
325    Provider: BlockReader + BalProvider + Clone + Unpin + 'static,
326{
327    /// Indexes all transactions in a block by transaction hash.
328    fn index_block_transactions(&mut self, block: &RecoveredBlock<Provider::Block>) {
329        let block_hash = block.hash();
330        for (tx_idx, tx) in block.body().transactions().iter().enumerate() {
331            self.tx_hash_index.insert(*tx.tx_hash(), (block_hash, tx_idx));
332        }
333    }
334
335    /// Removes transaction index entries for a reorged block.
336    fn remove_block_transactions(&mut self, block: &RecoveredBlock<Provider::Block>) {
337        for tx in block.body().transactions() {
338            self.tx_hash_index.remove(tx.tx_hash());
339        }
340    }
341
342    fn on_new_block(
343        &mut self,
344        block_hash: B256,
345        res: ProviderResult<Option<Arc<RecoveredBlock<Provider::Block>>>>,
346    ) {
347        if let Some(queued) = self.full_block_cache.remove(&block_hash) {
348            // send the response to queued senders
349            for tx in queued {
350                let _ = tx.send(res.clone());
351            }
352        }
353
354        // cache good block
355        if let Ok(Some(block)) = res {
356            self.full_block_cache.insert(block_hash, block);
357        }
358    }
359
360    fn on_new_receipts(
361        &mut self,
362        block_hash: B256,
363        res: ProviderResult<Option<Arc<Vec<Provider::Receipt>>>>,
364    ) {
365        if let Some(queued) = self.receipts_cache.remove(&block_hash) {
366            // send the response to queued senders
367            for tx in queued {
368                let _ = tx.send(res.clone());
369            }
370        }
371
372        // cache good receipts
373        if let Ok(Some(receipts)) = res {
374            self.receipts_cache.insert(block_hash, receipts);
375        }
376    }
377
378    fn on_new_bal(&mut self, block_hash: B256, res: ProviderResult<Option<CachedRevmBal>>) {
379        if let Some(queued) = self.bal_cache.remove(&block_hash) {
380            for tx in queued {
381                let _ = tx.send(res.clone());
382            }
383        }
384
385        if let Ok(Some(bal)) = res {
386            self.bal_cache.insert(block_hash, bal);
387        }
388    }
389
390    fn on_reorg_block(
391        &mut self,
392        block_hash: B256,
393        res: ProviderResult<Option<Arc<RecoveredBlock<Provider::Block>>>>,
394    ) {
395        if let Some(queued) = self.full_block_cache.remove(&block_hash) {
396            // send the response to queued senders
397            for tx in queued {
398                let _ = tx.send(res.clone());
399            }
400        }
401    }
402
403    fn on_reorg_receipts(
404        &mut self,
405        block_hash: B256,
406        res: ProviderResult<Option<Arc<Vec<Provider::Receipt>>>>,
407    ) {
408        if let Some(queued) = self.receipts_cache.remove(&block_hash) {
409            // send the response to queued senders
410            for tx in queued {
411                let _ = tx.send(res.clone());
412            }
413        }
414    }
415
416    fn on_reorg_bal(&mut self, block_hash: B256, res: ProviderResult<Option<CachedRevmBal>>) {
417        if let Some(queued) = self.bal_cache.remove(&block_hash) {
418            for tx in queued {
419                let _ = tx.send(res.clone());
420            }
421        }
422    }
423
424    /// Shrinks the queues but leaves some space for the next requests
425    fn shrink_queues(&mut self) {
426        let min_capacity = 2;
427        self.full_block_cache.shrink_to(min_capacity);
428        self.receipts_cache.shrink_to(min_capacity);
429        self.bal_cache.shrink_to(min_capacity);
430    }
431
432    fn update_cached_metrics(&mut self) {
433        self.full_block_cache.update_cached_metrics();
434        self.receipts_cache.update_cached_metrics();
435        self.bal_cache.update_cached_metrics();
436    }
437}
438
439impl<Provider> Future for EthStateCacheService<Provider, Runtime>
440where
441    Provider: BlockReader + BalProvider + Clone + Unpin + 'static,
442{
443    type Output = ();
444
445    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
446        let this = self.get_mut();
447
448        loop {
449            let Poll::Ready(action) = this.action_rx.poll_next_unpin(cx) else {
450                // shrink queues if we don't have any work to do
451                this.shrink_queues();
452                // gauges only need to be accurate once the batch of messages is drained, and only
453                // caches that changed during the batch republish theirs
454                this.update_cached_metrics();
455                return Poll::Pending;
456            };
457
458            match action {
459                None => {
460                    unreachable!("can't close")
461                }
462                Some(action) => {
463                    match action {
464                        CacheAction::GetCachedBlock { block_hash, response_tx } => {
465                            let _ =
466                                response_tx.send(this.full_block_cache.get(&block_hash).cloned());
467                        }
468                        CacheAction::GetCachedBal { block_hash, response_tx } => {
469                            let _ = response_tx.send(this.bal_cache.get(&block_hash).cloned());
470                        }
471                        CacheAction::GetCachedBlockAndReceipts { block_hash, response_tx } => {
472                            let block = this.full_block_cache.get(&block_hash).cloned();
473                            let receipts = this.receipts_cache.get(&block_hash).cloned();
474                            let _ = response_tx.send((block, receipts));
475                        }
476                        CacheAction::GetBlockWithSenders { block_hash, response_tx } => {
477                            if let Some(block) = this.full_block_cache.get(&block_hash).cloned() {
478                                let _ = response_tx.send(Ok(Some(block)));
479                                continue
480                            }
481
482                            // block is not in the cache, request it if this is the first consumer
483                            if this.full_block_cache.queue(block_hash, response_tx) {
484                                let provider = this.provider.clone();
485                                let action_tx = this.action_tx.clone();
486                                let rate_limiter = this.rate_limiter.clone();
487                                let mut action_sender =
488                                    ActionSender::new(CacheKind::Block, block_hash, action_tx);
489                                this.action_task_spawner.spawn_blocking_task(async move {
490                                    // Acquire permit
491                                    let _permit = rate_limiter.acquire().await;
492                                    // Only look in the database to prevent situations where we
493                                    // looking up the tree is blocking
494                                    let block_sender = provider
495                                        .sealed_block_with_senders(
496                                            BlockHashOrNumber::Hash(block_hash),
497                                            TransactionVariant::WithHash,
498                                        )
499                                        .map(|maybe_block| maybe_block.map(Arc::new));
500                                    action_sender.send_block(block_sender);
501                                });
502                            }
503                        }
504                        CacheAction::GetReceipts { block_hash, response_tx } => {
505                            // check if block is cached
506                            if let Some(receipts) = this.receipts_cache.get(&block_hash).cloned() {
507                                let _ = response_tx.send(Ok(Some(receipts)));
508                                continue
509                            }
510
511                            // block is not in the cache, request it if this is the first consumer
512                            if this.receipts_cache.queue(block_hash, response_tx) {
513                                let provider = this.provider.clone();
514                                let action_tx = this.action_tx.clone();
515                                let rate_limiter = this.rate_limiter.clone();
516                                let mut action_sender =
517                                    ActionSender::new(CacheKind::Receipt, block_hash, action_tx);
518                                this.action_task_spawner.spawn_blocking_task(async move {
519                                    // Acquire permit
520                                    let _permit = rate_limiter.acquire().await;
521                                    let res = provider
522                                        .receipts_by_block(block_hash.into())
523                                        .map(|maybe_receipts| maybe_receipts.map(Arc::new));
524
525                                    action_sender.send_receipts(res);
526                                });
527                            }
528                        }
529                        CacheAction::GetBal { block_hash, response_tx } => {
530                            if let Some(bal) = this.bal_cache.get(&block_hash).cloned() {
531                                let _ = response_tx.send(Ok(Some(bal)));
532                                continue
533                            }
534
535                            if this.bal_cache.queue(block_hash, response_tx) {
536                                let provider = this.provider.clone();
537                                let action_tx = this.action_tx.clone();
538                                let rate_limiter = this.rate_limiter.clone();
539                                let mut action_sender =
540                                    ActionSender::new(CacheKind::Bal, block_hash, action_tx);
541                                this.action_task_spawner.spawn_blocking_task(async move {
542                                    let _permit = rate_limiter.acquire().await;
543                                    let res = provider.get_bal_by_hash(block_hash).and_then(
544                                        |maybe_bal| {
545                                            maybe_bal.map(CachedRevmBal::try_from_raw).transpose()
546                                        },
547                                    );
548                                    action_sender.send_bal(res);
549                                });
550                            }
551                        }
552                        CacheAction::ReceiptsResult { block_hash, res } => {
553                            this.on_new_receipts(block_hash, res);
554                        }
555                        CacheAction::BalResult { block_hash, res } => {
556                            this.on_new_bal(block_hash, res);
557                        }
558                        CacheAction::BlockWithSendersResult { block_hash, res } => match res {
559                            Ok(Some(block_with_senders)) => {
560                                this.on_new_block(block_hash, Ok(Some(block_with_senders)));
561                            }
562                            Ok(None) => {
563                                this.on_new_block(block_hash, Ok(None));
564                            }
565                            Err(e) => {
566                                this.on_new_block(block_hash, Err(e));
567                            }
568                        },
569                        CacheAction::CacheNewCanonicalChain { chain_change } => {
570                            for block in chain_change.blocks {
571                                // Index transactions before caching the block
572                                this.index_block_transactions(&block);
573                                this.on_new_block(block.hash(), Ok(Some(block)));
574                            }
575
576                            for block_receipts in chain_change.receipts {
577                                this.on_new_receipts(
578                                    block_receipts.block_hash,
579                                    Ok(Some(block_receipts.receipts)),
580                                );
581                            }
582                        }
583                        CacheAction::RemoveReorgedChain { chain_change } => {
584                            for block in chain_change.blocks {
585                                let block_hash = block.hash();
586                                // Remove transaction index entries for reorged blocks
587                                this.remove_block_transactions(&block);
588                                this.on_reorg_block(block_hash, Ok(Some(block)));
589                                this.on_reorg_bal(block_hash, Ok(None));
590                            }
591
592                            for block_receipts in chain_change.receipts {
593                                this.on_reorg_receipts(
594                                    block_receipts.block_hash,
595                                    Ok(Some(block_receipts.receipts)),
596                                );
597                            }
598                        }
599                        CacheAction::GetTransactionByHash { tx_hash, response_tx } => {
600                            let result =
601                                this.tx_hash_index.get(&tx_hash).and_then(|(block_hash, idx)| {
602                                    let block = this.full_block_cache.get(block_hash).cloned()?;
603                                    let receipts = this.receipts_cache.get(block_hash).cloned();
604                                    Some(CachedTransaction::new(block, *idx, receipts))
605                                });
606                            let _ = response_tx.send(result);
607                        }
608                    };
609                }
610            }
611        }
612    }
613}
614
615/// All message variants sent through the channel
616enum CacheAction<B: Block, R> {
617    GetBlockWithSenders {
618        block_hash: B256,
619        response_tx: BlockWithSendersResponseSender<B>,
620    },
621    GetReceipts {
622        block_hash: B256,
623        response_tx: ReceiptsResponseSender<R>,
624    },
625    GetBal {
626        block_hash: B256,
627        response_tx: BalResponseSender,
628    },
629    GetCachedBlock {
630        block_hash: B256,
631        response_tx: CachedBlockResponseSender<B>,
632    },
633    GetCachedBal {
634        block_hash: B256,
635        response_tx: CachedBalResponseSender,
636    },
637    GetCachedBlockAndReceipts {
638        block_hash: B256,
639        response_tx: CachedBlockAndReceiptsResponseSender<B, R>,
640    },
641    BlockWithSendersResult {
642        block_hash: B256,
643        res: ProviderResult<Option<Arc<RecoveredBlock<B>>>>,
644    },
645    ReceiptsResult {
646        block_hash: B256,
647        res: ProviderResult<Option<Arc<Vec<R>>>>,
648    },
649    BalResult {
650        block_hash: B256,
651        res: ProviderResult<Option<CachedRevmBal>>,
652    },
653    CacheNewCanonicalChain {
654        chain_change: ChainChange<B, R>,
655    },
656    RemoveReorgedChain {
657        chain_change: ChainChange<B, R>,
658    },
659    /// Look up a transaction's cached data by its hash
660    GetTransactionByHash {
661        tx_hash: TxHash,
662        response_tx: TransactionHashResponseSender<B, R>,
663    },
664}
665
666struct BlockReceipts<R> {
667    block_hash: B256,
668    receipts: Arc<Vec<R>>,
669}
670
671/// A change of the canonical chain
672struct ChainChange<B: Block, R> {
673    blocks: Vec<Arc<RecoveredBlock<B>>>,
674    receipts: Vec<BlockReceipts<R>>,
675}
676
677impl<B: Block, R: Clone> ChainChange<B, R> {
678    fn new<N>(chain: Arc<Chain<N>>) -> Self
679    where
680        N: NodePrimitives<Block = B, Receipt = R>,
681    {
682        let (blocks, receipts): (Vec<_>, Vec<_>) = chain
683            .blocks_and_receipts()
684            .map(|(block, receipts)| {
685                let block_receipts = BlockReceipts {
686                    block_hash: block.hash(),
687                    receipts: Arc::new(receipts.clone()),
688                };
689                (Arc::clone(block), block_receipts)
690            })
691            .unzip();
692        Self { blocks, receipts }
693    }
694}
695
696/// Identifier for the caches.
697#[derive(Copy, Clone, Debug)]
698enum CacheKind {
699    Block,
700    Receipt,
701    Bal,
702}
703
704/// Drop aware sender struct that ensures a response is always emitted even if the db task panics
705/// before a result could be sent.
706///
707/// This type wraps a sender and in case the sender is still present on drop emit an error response.
708#[derive(Debug)]
709struct ActionSender<B: Block, R: Send + Sync> {
710    kind: CacheKind,
711    blockhash: B256,
712    tx: Option<UnboundedSender<CacheAction<B, R>>>,
713}
714
715impl<R: Send + Sync, B: Block> ActionSender<B, R> {
716    const fn new(kind: CacheKind, blockhash: B256, tx: UnboundedSender<CacheAction<B, R>>) -> Self {
717        Self { kind, blockhash, tx: Some(tx) }
718    }
719
720    fn send_block(&mut self, block_sender: Result<Option<Arc<RecoveredBlock<B>>>, ProviderError>) {
721        if let Some(tx) = self.tx.take() {
722            let _ = tx.send(CacheAction::BlockWithSendersResult {
723                block_hash: self.blockhash,
724                res: block_sender,
725            });
726        }
727    }
728
729    fn send_receipts(&mut self, receipts: Result<Option<Arc<Vec<R>>>, ProviderError>) {
730        if let Some(tx) = self.tx.take() {
731            let _ =
732                tx.send(CacheAction::ReceiptsResult { block_hash: self.blockhash, res: receipts });
733        }
734    }
735
736    fn send_bal(&mut self, bal: Result<Option<CachedRevmBal>, ProviderError>) {
737        if let Some(tx) = self.tx.take() {
738            let _ = tx.send(CacheAction::BalResult { block_hash: self.blockhash, res: bal });
739        }
740    }
741}
742impl<R: Send + Sync, B: Block> Drop for ActionSender<B, R> {
743    fn drop(&mut self) {
744        if let Some(tx) = self.tx.take() {
745            let msg = match self.kind {
746                CacheKind::Block => CacheAction::BlockWithSendersResult {
747                    block_hash: self.blockhash,
748                    res: Err(CacheServiceUnavailable.into()),
749                },
750                CacheKind::Receipt => CacheAction::ReceiptsResult {
751                    block_hash: self.blockhash,
752                    res: Err(CacheServiceUnavailable.into()),
753                },
754                CacheKind::Bal => CacheAction::BalResult {
755                    block_hash: self.blockhash,
756                    res: Err(CacheServiceUnavailable.into()),
757                },
758            };
759            let _ = tx.send(msg);
760        }
761    }
762}
763
764/// Awaits for new chain events and directly inserts them into the cache so they're available
765/// immediately before they need to be fetched from disk.
766///
767/// Reorged blocks are removed from the cache.
768pub async fn cache_new_blocks_task<St, N: NodePrimitives>(
769    eth_state_cache: EthStateCache<N>,
770    mut events: St,
771) where
772    St: Stream<Item = CanonStateNotification<N>> + Unpin + 'static,
773{
774    while let Some(event) = events.next().await {
775        if let Some(reverted) = event.reverted() {
776            let chain_change = ChainChange::new(reverted);
777
778            let _ =
779                eth_state_cache.to_service.send(CacheAction::RemoveReorgedChain { chain_change });
780        }
781
782        let chain_change = ChainChange::new(event.committed());
783
784        let _ =
785            eth_state_cache.to_service.send(CacheAction::CacheNewCanonicalChain { chain_change });
786    }
787}
788
789/// Cached decoded revm BAL.
790#[derive(Clone, Debug)]
791pub(crate) struct CachedRevmBal(Arc<DecodedBal<Arc<RevmBal>>>);
792
793impl CachedRevmBal {
794    /// Creates a cached revm BAL from an owned decoded BAL.
795    #[inline]
796    fn new(bal: DecodedBal<Arc<RevmBal>>) -> Self {
797        Self(Arc::new(bal))
798    }
799
800    /// Decodes raw BAL bytes into the representation used by revm.
801    fn try_from_raw(raw: Bytes) -> ProviderResult<Self> {
802        DecodedBal::from_rlp_bytes(raw)
803            .map_err(Into::into)
804            .and_then(|decoded| {
805                decoded.try_map(|bal| {
806                    RevmBal::try_from(Vec::from(bal)).map(Arc::new).map_err(ProviderError::other)
807                })
808            })
809            .map(Self::new)
810    }
811}
812
813impl InMemorySize for CachedRevmBal {
814    fn size(&self) -> usize {
815        core::mem::size_of::<Self>() + decoded_revm_bal_size(&self.0)
816    }
817}
818
819fn decoded_revm_bal_size(bal: &DecodedBal<Arc<RevmBal>>) -> usize {
820    core::mem::size_of::<DecodedBal<Arc<RevmBal>>>() +
821        bal.as_raw().len() +
822        revm_bal_size(bal.as_bal())
823}
824
825fn revm_bal_size(bal: &Arc<RevmBal>) -> usize {
826    core::mem::size_of::<RevmBal>() +
827        bal.accounts.capacity() * core::mem::size_of::<(Address, RevmAccountBal)>() +
828        bal.accounts.values().map(revm_account_bal_heap_size).sum::<usize>()
829}
830
831fn revm_account_bal_heap_size(account: &RevmAccountBal) -> usize {
832    revm_account_info_bal_heap_size(&account.account_info) +
833        revm_storage_bal_heap_size(&account.storage)
834}
835
836fn revm_account_info_bal_heap_size(account_info: &RevmAccountInfoBal) -> usize {
837    revm_bal_writes_heap_size(&account_info.nonce, |_| 0) +
838        revm_bal_writes_heap_size(&account_info.balance, |_| 0) +
839        revm_bal_writes_heap_size(&account_info.code, revm_code_write_heap_size)
840}
841
842fn revm_storage_bal_heap_size(storage: &RevmStorageBal) -> usize {
843    storage.storage.len() * core::mem::size_of::<(StorageKey, RevmBalWrites<StorageValue>)>() +
844        storage
845            .storage
846            .values()
847            .map(|writes| revm_bal_writes_heap_size(writes, |_| 0))
848            .sum::<usize>()
849}
850
851fn revm_bal_writes_heap_size<T, F>(writes: &RevmBalWrites<T>, mut item_heap_size: F) -> usize
852where
853    T: PartialEq + Clone,
854    F: FnMut(&T) -> usize,
855{
856    writes.writes.capacity() * core::mem::size_of::<(u64, T)>() +
857        writes.writes.iter().map(|(_, item)| item_heap_size(item)).sum::<usize>()
858}
859
860fn revm_code_write_heap_size((_, bytecode): &(B256, Bytecode)) -> usize {
861    bytecode.bytes_ref().len()
862}
863
864#[cfg(test)]
865mod tests {
866    use super::*;
867    use alloy_consensus::{transaction::TransactionMeta, Header};
868    use alloy_eip7928::BlockAccessIndex;
869    use alloy_eips::{BlockHashOrNumber, NumHash};
870    use alloy_primitives::{Address, BlockHash, BlockNumber, Bytes, Signature, TxHash, TxNumber};
871    use core::ops::{RangeBounds, RangeInclusive};
872    use reth_db_models::StoredBlockBodyIndices;
873    use reth_ethereum_primitives::{
874        Block, BlockBody, EthPrimitives, Receipt, Transaction, TransactionSigned,
875    };
876    use reth_primitives_traits::{RecoveredBlock, SealedHeader};
877    use reth_storage_api::{
878        noop::NoopProvider, BalProvider, BalStore, BalStoreHandle, BlockBodyIndicesProvider,
879        BlockHashReader, BlockNumReader, BlockReader, BlockSource, HeaderProvider, ReceiptProvider,
880        TransactionVariant, TransactionsProvider,
881    };
882    use std::sync::atomic::{AtomicUsize, Ordering};
883
884    fn test_service() -> EthStateCacheService<NoopProvider, Runtime> {
885        let (_cache, service) = EthStateCache::<EthPrimitives>::create(
886            NoopProvider::default(),
887            Runtime::test(),
888            EthStateCacheConfig {
889                max_blocks: 4,
890                max_receipts: 4,
891                max_bals: 4,
892                max_concurrent_db_requests: 1,
893                max_cached_tx_hashes: 16,
894            },
895        );
896        service
897    }
898
899    fn test_decoded_revm_bal() -> DecodedBal<Arc<RevmBal>> {
900        DecodedBal::new(Arc::new(RevmBal::default()), Bytes::from_static(&[0xc0]))
901    }
902
903    fn test_block() -> RecoveredBlock<Block> {
904        RecoveredBlock::new_unhashed(
905            Block {
906                header: Header { number: 1, ..Default::default() },
907                body: BlockBody {
908                    transactions: vec![TransactionSigned::new_unhashed(
909                        Transaction::Legacy(Default::default()),
910                        Signature::test_signature(),
911                    )],
912                    ..Default::default()
913                },
914            },
915            vec![Address::ZERO],
916        )
917    }
918
919    #[test]
920    fn reorg_removes_tx_hash_index_entries_unconditionally() {
921        let mut service = test_service();
922        let block = test_block();
923        let tx_hash = *block.body().transactions().next().expect("test transaction").tx_hash();
924
925        service.tx_hash_index.insert(tx_hash, (B256::repeat_byte(0x33), 0));
926
927        service.remove_block_transactions(&block);
928
929        assert!(service.tx_hash_index.get(&tx_hash).is_none());
930    }
931
932    #[test]
933    fn reorg_evicts_cached_bal() {
934        let mut service = test_service();
935        let block_hash = B256::repeat_byte(0x44);
936
937        assert!(service.bal_cache.insert(block_hash, CachedRevmBal::new(test_decoded_revm_bal())));
938        assert!(service.bal_cache.get(&block_hash).is_some());
939
940        service.on_reorg_bal(block_hash, Ok(None));
941
942        assert!(service.bal_cache.get(&block_hash).is_none());
943    }
944
945    #[test]
946    fn reorg_forwards_bal_to_queued_requests() {
947        let mut service = test_service();
948        let block_hash = B256::repeat_byte(0x55);
949        let (response_tx, mut response_rx) = oneshot::channel();
950        let bal = CachedRevmBal::new(test_decoded_revm_bal());
951
952        assert!(service.bal_cache.queue(block_hash, response_tx));
953
954        service.on_reorg_bal(block_hash, Ok(Some(bal)));
955
956        let bal = response_rx.try_recv().expect("queued BAL response").expect("BAL result");
957
958        assert!(bal.is_some());
959    }
960
961    #[test]
962    fn cached_revm_bal_size_accounts_for_nested_allocations() {
963        let mut account = RevmAccountBal::default();
964        account.account_info.nonce.writes.push((BlockAccessIndex::new(1), 1));
965        account
966            .account_info
967            .balance
968            .writes
969            .push((BlockAccessIndex::new(2), StorageValue::from(1u64)));
970        account.account_info.code.writes.push((
971            BlockAccessIndex::new(3),
972            (B256::repeat_byte(0xaa), Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00]))),
973        ));
974        account.storage.storage.insert(
975            StorageKey::from(1u64),
976            RevmBalWrites::new(vec![(BlockAccessIndex::new(4), StorageValue::from(2u64))]),
977        );
978
979        let mut bal = RevmBal::default();
980        bal.accounts.insert(Address::ZERO, account);
981
982        let raw = Bytes::from_static(&[0xc0, 0x01, 0x02]);
983        let previous_estimate = core::mem::size_of::<CachedRevmBal>() +
984            core::mem::size_of::<DecodedBal<Arc<RevmBal>>>() +
985            raw.len() +
986            core::mem::size_of::<RevmBal>();
987        assert!(CachedRevmBal::new(DecodedBal::new(Arc::new(bal), raw)).size() > previous_estimate);
988    }
989
990    #[tokio::test]
991    async fn get_bal_uses_cached_revm_bal() {
992        let fetches = Arc::new(AtomicUsize::default());
993        let provider = TestBalProvider::new(fetches.clone());
994        let cache = EthStateCache::<EthPrimitives>::spawn_with(
995            provider,
996            EthStateCacheConfig {
997                max_blocks: 0,
998                max_receipts: 0,
999                max_bals: 4,
1000                max_concurrent_db_requests: 1,
1001                max_cached_tx_hashes: 0,
1002            },
1003            Runtime::test(),
1004        );
1005        let block_hash = B256::repeat_byte(0x66);
1006
1007        assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1008        assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1009
1010        assert_eq!(fetches.load(Ordering::SeqCst), 1);
1011    }
1012
1013    #[tokio::test]
1014    async fn get_recovered_block_and_maybe_bal_does_not_fetch_bal() {
1015        let bal_fetches = Arc::new(AtomicUsize::default());
1016        let block = test_block();
1017        let block_hash = block.hash();
1018        let provider = TestBalProvider::new(bal_fetches.clone()).with_block(block);
1019        let cache = EthStateCache::<EthPrimitives>::spawn_with(
1020            provider,
1021            EthStateCacheConfig {
1022                max_blocks: 4,
1023                max_receipts: 0,
1024                max_bals: 4,
1025                max_concurrent_db_requests: 1,
1026                max_cached_tx_hashes: 0,
1027            },
1028            Runtime::test(),
1029        );
1030
1031        let (returned_block, bal) = cache
1032            .get_recovered_block_and_maybe_bal(block_hash)
1033            .await
1034            .unwrap()
1035            .expect("block exists");
1036        assert_eq!(returned_block.hash(), block_hash);
1037        assert!(bal.is_none());
1038        assert_eq!(bal_fetches.load(Ordering::SeqCst), 0);
1039
1040        assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1041
1042        let (_, bal) = cache
1043            .get_recovered_block_and_maybe_bal(block_hash)
1044            .await
1045            .unwrap()
1046            .expect("block exists");
1047        assert!(bal.is_some());
1048        assert_eq!(bal_fetches.load(Ordering::SeqCst), 1);
1049    }
1050
1051    #[tokio::test]
1052    async fn concurrent_get_bal_requests_share_fetch() {
1053        let fetches = Arc::new(AtomicUsize::default());
1054        let provider = TestBalProvider::new(fetches.clone());
1055        let cache = EthStateCache::<EthPrimitives>::spawn_with(
1056            provider,
1057            EthStateCacheConfig {
1058                max_blocks: 0,
1059                max_receipts: 0,
1060                max_bals: 4,
1061                max_concurrent_db_requests: 1,
1062                max_cached_tx_hashes: 0,
1063            },
1064            Runtime::test(),
1065        );
1066        let block_hash = B256::repeat_byte(0x77);
1067
1068        let (first, second) = tokio::join!(cache.get_bal(block_hash), cache.get_bal(block_hash));
1069
1070        assert!(first.unwrap().is_some());
1071        assert!(second.unwrap().is_some());
1072        assert_eq!(fetches.load(Ordering::SeqCst), 1);
1073    }
1074
1075    #[derive(Clone, Debug, Default)]
1076    struct TestBalProvider {
1077        bal_store: BalStoreHandle,
1078        block: Option<RecoveredBlock<Block>>,
1079    }
1080
1081    impl TestBalProvider {
1082        fn new(fetches: Arc<AtomicUsize>) -> Self {
1083            Self { bal_store: BalStoreHandle::new(TestBalStore { fetches }), block: None }
1084        }
1085
1086        fn with_block(mut self, block: RecoveredBlock<Block>) -> Self {
1087            self.block = Some(block);
1088            self
1089        }
1090    }
1091
1092    impl BalProvider for TestBalProvider {
1093        fn bal_store(&self) -> &BalStoreHandle {
1094            &self.bal_store
1095        }
1096    }
1097
1098    #[derive(Debug)]
1099    struct TestBalStore {
1100        fetches: Arc<AtomicUsize>,
1101    }
1102
1103    impl BalStore for TestBalStore {
1104        fn insert(&self, _num_hash: NumHash, _bal: reth_storage_api::RawBal) -> ProviderResult<()> {
1105            Ok(())
1106        }
1107
1108        fn prune(&self, _tip: BlockNumber) -> ProviderResult<usize> {
1109            Ok(0)
1110        }
1111
1112        fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
1113            self.fetches.fetch_add(1, Ordering::SeqCst);
1114            Ok(block_hashes.iter().map(|_| Some(Bytes::from_static(&[0xc0]))).collect())
1115        }
1116
1117        fn bal_stream(&self) -> reth_storage_api::BalNotificationStream {
1118            reth_storage_api::NoopBalStore.bal_stream()
1119        }
1120    }
1121
1122    impl BlockHashReader for TestBalProvider {
1123        fn block_hash(&self, _number: BlockNumber) -> ProviderResult<Option<B256>> {
1124            Ok(None)
1125        }
1126
1127        fn canonical_hashes_range(
1128            &self,
1129            _start: BlockNumber,
1130            _end: BlockNumber,
1131        ) -> ProviderResult<Vec<B256>> {
1132            Ok(Vec::new())
1133        }
1134    }
1135
1136    impl BlockNumReader for TestBalProvider {
1137        fn chain_info(&self) -> ProviderResult<reth_chainspec::ChainInfo> {
1138            Ok(reth_chainspec::ChainInfo::default())
1139        }
1140
1141        fn best_block_number(&self) -> ProviderResult<BlockNumber> {
1142            Ok(0)
1143        }
1144
1145        fn last_block_number(&self) -> ProviderResult<BlockNumber> {
1146            Ok(0)
1147        }
1148
1149        fn block_number(&self, _hash: B256) -> ProviderResult<Option<BlockNumber>> {
1150            Ok(None)
1151        }
1152    }
1153
1154    impl HeaderProvider for TestBalProvider {
1155        type Header = Header;
1156
1157        fn header(&self, _block_hash: BlockHash) -> ProviderResult<Option<Self::Header>> {
1158            Ok(None)
1159        }
1160
1161        fn header_by_number(&self, _num: u64) -> ProviderResult<Option<Self::Header>> {
1162            Ok(None)
1163        }
1164
1165        fn headers_range(
1166            &self,
1167            _range: impl RangeBounds<BlockNumber>,
1168        ) -> ProviderResult<Vec<Self::Header>> {
1169            Ok(Vec::new())
1170        }
1171
1172        fn sealed_header(
1173            &self,
1174            _number: BlockNumber,
1175        ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
1176            Ok(None)
1177        }
1178
1179        fn sealed_headers_while(
1180            &self,
1181            _range: impl RangeBounds<BlockNumber>,
1182            _predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
1183        ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
1184            Ok(Vec::new())
1185        }
1186    }
1187
1188    impl BlockBodyIndicesProvider for TestBalProvider {
1189        fn block_body_indices(&self, _num: u64) -> ProviderResult<Option<StoredBlockBodyIndices>> {
1190            Ok(None)
1191        }
1192
1193        fn block_body_indices_range(
1194            &self,
1195            _range: RangeInclusive<BlockNumber>,
1196        ) -> ProviderResult<Vec<StoredBlockBodyIndices>> {
1197            Ok(Vec::new())
1198        }
1199    }
1200
1201    impl TransactionsProvider for TestBalProvider {
1202        type Transaction = TransactionSigned;
1203
1204        fn transaction_id(&self, _tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
1205            Ok(None)
1206        }
1207
1208        fn transaction_by_id(&self, _id: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
1209            Ok(None)
1210        }
1211
1212        fn transaction_by_id_unhashed(
1213            &self,
1214            _id: TxNumber,
1215        ) -> ProviderResult<Option<Self::Transaction>> {
1216            Ok(None)
1217        }
1218
1219        fn transaction_by_hash(&self, _hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
1220            Ok(None)
1221        }
1222
1223        fn transaction_by_hash_with_meta(
1224            &self,
1225            _hash: TxHash,
1226        ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
1227            Ok(None)
1228        }
1229
1230        fn transactions_by_block(
1231            &self,
1232            _block: BlockHashOrNumber,
1233        ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
1234            Ok(None)
1235        }
1236
1237        fn transactions_by_block_range(
1238            &self,
1239            _range: impl RangeBounds<BlockNumber>,
1240        ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
1241            Ok(Vec::new())
1242        }
1243
1244        fn transactions_by_tx_range(
1245            &self,
1246            _range: impl RangeBounds<TxNumber>,
1247        ) -> ProviderResult<Vec<Self::Transaction>> {
1248            Ok(Vec::new())
1249        }
1250
1251        fn senders_by_tx_range(
1252            &self,
1253            _range: impl RangeBounds<TxNumber>,
1254        ) -> ProviderResult<Vec<Address>> {
1255            Ok(Vec::new())
1256        }
1257
1258        fn transaction_sender(&self, _id: TxNumber) -> ProviderResult<Option<Address>> {
1259            Ok(None)
1260        }
1261    }
1262
1263    impl ReceiptProvider for TestBalProvider {
1264        type Receipt = Receipt;
1265
1266        fn receipt(&self, _id: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
1267            Ok(None)
1268        }
1269
1270        fn receipt_by_hash(&self, _hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
1271            Ok(None)
1272        }
1273
1274        fn receipts_by_block(
1275            &self,
1276            _block: BlockHashOrNumber,
1277        ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
1278            Ok(None)
1279        }
1280
1281        fn receipts_by_tx_range(
1282            &self,
1283            _range: impl RangeBounds<TxNumber>,
1284        ) -> ProviderResult<Vec<Self::Receipt>> {
1285            Ok(Vec::new())
1286        }
1287
1288        fn receipts_by_block_range(
1289            &self,
1290            _block_range: RangeInclusive<BlockNumber>,
1291        ) -> ProviderResult<Vec<Vec<Self::Receipt>>> {
1292            Ok(Vec::new())
1293        }
1294    }
1295
1296    impl BlockReader for TestBalProvider {
1297        type Block = Block;
1298
1299        fn find_block_by_hash(
1300            &self,
1301            _hash: B256,
1302            _source: BlockSource,
1303        ) -> ProviderResult<Option<Self::Block>> {
1304            Ok(None)
1305        }
1306
1307        fn block(&self, _id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> {
1308            Ok(None)
1309        }
1310
1311        fn pending_block(&self) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
1312            Ok(None)
1313        }
1314
1315        fn pending_block_and_receipts(
1316            &self,
1317        ) -> ProviderResult<Option<(RecoveredBlock<Self::Block>, Vec<Self::Receipt>)>> {
1318            Ok(None)
1319        }
1320
1321        fn recovered_block(
1322            &self,
1323            _id: BlockHashOrNumber,
1324            _transaction_kind: TransactionVariant,
1325        ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
1326            Ok(None)
1327        }
1328
1329        fn sealed_block_with_senders(
1330            &self,
1331            _id: BlockHashOrNumber,
1332            _transaction_kind: TransactionVariant,
1333        ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
1334            Ok(self.block.clone())
1335        }
1336
1337        fn block_range(
1338            &self,
1339            _range: RangeInclusive<BlockNumber>,
1340        ) -> ProviderResult<Vec<Self::Block>> {
1341            Ok(Vec::new())
1342        }
1343
1344        fn block_with_senders_range(
1345            &self,
1346            _range: RangeInclusive<BlockNumber>,
1347        ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
1348            Ok(Vec::new())
1349        }
1350
1351        fn recovered_block_range(
1352            &self,
1353            _range: RangeInclusive<BlockNumber>,
1354        ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
1355            Ok(Vec::new())
1356        }
1357
1358        fn block_by_transaction_id(&self, _id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
1359            Ok(None)
1360        }
1361    }
1362}