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, StorageChange};
7use alloy_eips::BlockHashOrNumber;
8use alloy_primitives::{map::FbBuildHasher, Address, Bytes, TxHash, B256, U256};
9use evm2::{
10    bytecode::Bytecode,
11    evm::{
12        AccountBal as EvmAccountBal, AccountInfoBal as EvmAccountInfoBal, Bal as EvmBal,
13        BalChange as EvmBalChange, BalChanges as EvmBalChanges, BalCodeChange as EvmBalCodeChange,
14        StorageBal as EvmStorageBal,
15    },
16};
17use futures::{Stream, StreamExt};
18use reth_chain_state::CanonStateNotification;
19use reth_errors::{ProviderError, ProviderResult};
20use reth_execution_types::{Chain, DecodedEvmBal};
21use reth_lru::LruMap;
22use reth_primitives_traits::{Block, BlockBody, InMemorySize, NodePrimitives, RecoveredBlock};
23use reth_storage_api::{BalProvider, BlockReader, TransactionVariant};
24use reth_tasks::Runtime;
25use std::{
26    cell::LazyCell,
27    collections::VecDeque,
28    future::Future,
29    pin::Pin,
30    sync::Arc,
31    task::{Context, Poll},
32    time::Duration,
33};
34use tokio::{
35    sync::{
36        mpsc::{unbounded_channel, UnboundedSender},
37        oneshot, OwnedSemaphorePermit, Semaphore,
38    },
39    time::{Instant, Interval, MissedTickBehavior},
40};
41use tokio_stream::wrappers::UnboundedReceiverStream;
42
43pub mod config;
44pub mod db;
45pub mod metrics;
46pub mod multi_consumer;
47
48/// The type that can send the response to a requested [`RecoveredBlock`]
49type BlockWithSendersResponseSender<B> =
50    oneshot::Sender<ProviderResult<Option<Arc<RecoveredBlock<B>>>>>;
51
52/// The type that can send the response to the requested receipts of a block.
53type ReceiptsResponseSender<R> = oneshot::Sender<ProviderResult<Option<Arc<Vec<R>>>>>;
54
55type CachedBlockResponseSender<B> = oneshot::Sender<Option<Arc<RecoveredBlock<B>>>>;
56
57type CachedBalResponseSender = oneshot::Sender<Option<CachedBal>>;
58
59type CachedBlockAndReceiptsResponseSender<B, R> =
60    oneshot::Sender<(Option<Arc<RecoveredBlock<B>>>, Option<Arc<Vec<R>>>)>;
61
62/// The type that can send the response for a transaction hash lookup
63type TransactionHashResponseSender<B, R> = oneshot::Sender<Option<CachedTransaction<B, R>>>;
64
65/// The type that can send the response to a requested evm2 BAL.
66type BalResponseSender = oneshot::Sender<ProviderResult<Option<CachedBal>>>;
67
68type BlockLruCache<B> = MultiConsumerLruCache<
69    B256,
70    Arc<RecoveredBlock<B>>,
71    BlockWithSendersResponseSender<B>,
72    FbBuildHasher<32>,
73>;
74
75type ReceiptsLruCache<R> =
76    MultiConsumerLruCache<B256, Arc<Vec<R>>, ReceiptsResponseSender<R>, FbBuildHasher<32>>;
77
78type BalLruCache = MultiConsumerLruCache<B256, CachedBal, BalResponseSender, FbBuildHasher<32>>;
79
80/// Provides async access to cached eth data
81///
82/// This is the frontend for the async caching service which manages cached data on a different
83/// task.
84#[derive(Debug)]
85pub struct EthStateCache<N: NodePrimitives> {
86    to_service: UnboundedSender<CacheAction<N::Block, N::Receipt>>,
87}
88
89impl<N: NodePrimitives> Clone for EthStateCache<N> {
90    fn clone(&self) -> Self {
91        Self { to_service: self.to_service.clone() }
92    }
93}
94
95impl<N: NodePrimitives> EthStateCache<N> {
96    /// Creates and returns both [`EthStateCache`] frontend and the memory bound service.
97    fn create<Provider>(
98        provider: Provider,
99        action_task_spawner: Runtime,
100        config: EthStateCacheConfig,
101    ) -> (Self, EthStateCacheService<Provider>)
102    where
103        Provider: BlockReader<Block = N::Block, Receipt = N::Receipt> + BalProvider,
104    {
105        let EthStateCacheConfig {
106            max_blocks,
107            max_receipts,
108            max_bals,
109            max_blocks_bytes,
110            max_receipts_bytes,
111            max_bals_bytes,
112            idle_timeout,
113            cache_computed_bals: _,
114            prewarm_bals: _,
115            max_concurrent_db_requests,
116            max_cached_tx_hashes,
117        } = config;
118        let eviction_interval = {
119            let _guard = action_task_spawner.handle().enter();
120            // Clamp short timeouts to avoid busy polling. Expiration is best effort between ticks.
121            let period = (idle_timeout / 4).clamp(Duration::from_secs(1), Duration::from_secs(60));
122            let mut interval = tokio::time::interval_at(Instant::now() + period, period);
123            interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
124            interval
125        };
126        let (to_service, rx) = unbounded_channel();
127
128        let service = EthStateCacheService {
129            provider,
130            full_block_cache: BlockLruCache::new(max_blocks, max_blocks_bytes, "blocks"),
131            receipts_cache: ReceiptsLruCache::new(max_receipts, max_receipts_bytes, "receipts"),
132            bal_cache: BalLruCache::new(max_bals, max_bals_bytes, "bals"),
133            idle_timeout,
134            eviction_interval,
135            eviction_pending: false,
136            action_tx: to_service.clone(),
137            action_rx: UnboundedReceiverStream::new(rx),
138            action_task_spawner,
139            rate_limiter: Arc::new(Semaphore::new(max_concurrent_db_requests)),
140            pending_fetches: VecDeque::new(),
141            tx_hash_index: LruMap::with_hasher(max_cached_tx_hashes, Default::default()),
142        };
143        let cache = Self { to_service };
144        (cache, service)
145    }
146
147    /// Creates a new async LRU backed cache service task and spawns it to a new task via the given
148    /// spawner.
149    ///
150    /// Entry counts and estimated payload bytes bound the cache. Idle entries expire periodically.
151    pub fn spawn_with<Provider>(
152        provider: Provider,
153        config: EthStateCacheConfig,
154        executor: Runtime,
155    ) -> Self
156    where
157        Provider: BlockReader<Block = N::Block, Receipt = N::Receipt>
158            + BalProvider
159            + Clone
160            + Unpin
161            + 'static,
162    {
163        let (this, service) = Self::create(provider, executor.clone(), config);
164        executor.spawn_critical_task("eth state cache", service);
165        this
166    }
167
168    /// Requests the  [`RecoveredBlock`] for the block hash
169    ///
170    /// Returns `None` if the block does not exist.
171    pub async fn get_recovered_block(
172        &self,
173        block_hash: B256,
174    ) -> ProviderResult<Option<Arc<RecoveredBlock<N::Block>>>> {
175        let (response_tx, rx) = oneshot::channel();
176        let _ = self.to_service.send(CacheAction::GetBlockWithSenders { block_hash, response_tx });
177        rx.await.map_err(|_| CacheServiceUnavailable)?
178    }
179
180    /// Requests the block for the given block hash if it is cached.
181    pub async fn get_maybe_block(
182        &self,
183        block_hash: B256,
184    ) -> ProviderResult<Option<Arc<RecoveredBlock<N::Block>>>> {
185        let (response_tx, rx) = oneshot::channel();
186        let _ = self.to_service.send(CacheAction::GetCachedBlock { block_hash, response_tx });
187        rx.await.map_err(|_| CacheServiceUnavailable.into())
188    }
189
190    /// Requests the receipts for the block hash
191    ///
192    /// Returns `None` if the block was not found.
193    pub async fn get_receipts(
194        &self,
195        block_hash: B256,
196    ) -> ProviderResult<Option<Arc<Vec<N::Receipt>>>> {
197        let (response_tx, rx) = oneshot::channel();
198        let _ = self.to_service.send(CacheAction::GetReceipts { block_hash, response_tx });
199        rx.await.map_err(|_| CacheServiceUnavailable)?
200    }
201
202    /// Fetches both receipts and block for the given block hash.
203    pub async fn get_block_and_receipts(
204        &self,
205        block_hash: B256,
206    ) -> ProviderResult<Option<(Arc<RecoveredBlock<N::Block>>, Arc<Vec<N::Receipt>>)>> {
207        let block = self.get_recovered_block(block_hash);
208        let receipts = self.get_receipts(block_hash);
209
210        let (block, receipts) = futures::try_join!(block, receipts)?;
211
212        Ok(block.zip(receipts))
213    }
214
215    /// Retrieves the block and its BAL if the BAL is already cached.
216    ///
217    /// The block is fetched if it is not cached. Returns `None` if the block does not exist.
218    pub async fn get_recovered_block_and_maybe_bal(
219        &self,
220        block_hash: B256,
221    ) -> ProviderResult<Option<(Arc<RecoveredBlock<N::Block>>, Option<Arc<DecodedBal<Arc<EvmBal>>>>)>>
222    {
223        let (response_tx, rx) = oneshot::channel();
224        let _ = self.to_service.send(CacheAction::GetCachedBal { block_hash, response_tx });
225
226        let block = self.get_recovered_block(block_hash);
227        let (block, bal) = futures::join!(block, rx);
228
229        let bal = bal.map_err(|_| CacheServiceUnavailable)?.map(|cached| cached.0);
230        Ok(block?.map(|block| (block, bal)))
231    }
232
233    /// Retrieves receipts and blocks from cache if block is in the cache, otherwise only receipts.
234    pub async fn get_receipts_and_maybe_block(
235        &self,
236        block_hash: B256,
237    ) -> ProviderResult<Option<(Arc<Vec<N::Receipt>>, Option<Arc<RecoveredBlock<N::Block>>>)>> {
238        let (response_tx, rx) = oneshot::channel();
239        let _ = self.to_service.send(CacheAction::GetCachedBlock { block_hash, response_tx });
240
241        let receipts = self.get_receipts(block_hash);
242
243        let (receipts, block) = futures::join!(receipts, rx);
244
245        let block = block.map_err(|_| CacheServiceUnavailable)?;
246        Ok(receipts?.map(|r| (r, block)))
247    }
248
249    /// Retrieves both block and receipts from cache if available.
250    pub async fn maybe_cached_block_and_receipts(
251        &self,
252        block_hash: B256,
253    ) -> ProviderResult<(Option<Arc<RecoveredBlock<N::Block>>>, Option<Arc<Vec<N::Receipt>>>)> {
254        let (response_tx, rx) = oneshot::channel();
255        let _ = self
256            .to_service
257            .send(CacheAction::GetCachedBlockAndReceipts { block_hash, response_tx });
258        rx.await.map_err(|_| CacheServiceUnavailable.into())
259    }
260
261    /// Looks up a transaction by its hash in the cache index.
262    ///
263    /// Returns the cached block, transaction index, and optionally receipts if the transaction
264    /// is in a cached block.
265    pub async fn get_transaction_by_hash(
266        &self,
267        tx_hash: TxHash,
268    ) -> Option<CachedTransaction<N::Block, N::Receipt>> {
269        let (response_tx, rx) = oneshot::channel();
270        let _ = self.to_service.send(CacheAction::GetTransactionByHash { tx_hash, response_tx });
271        rx.await.ok()?
272    }
273
274    /// Requests the evm2 BAL for the block hash.
275    ///
276    /// Returns `None` if the BAL does not exist.
277    pub async fn get_bal(
278        &self,
279        block_hash: B256,
280    ) -> ProviderResult<Option<Arc<DecodedBal<Arc<EvmBal>>>>> {
281        let (response_tx, rx) = oneshot::channel();
282        let _ = self.to_service.send(CacheAction::GetBal { block_hash, response_tx });
283        rx.await
284            .map_err(|_| CacheServiceUnavailable)?
285            .map(|maybe_bal| maybe_bal.map(|cached| cached.0))
286    }
287
288    /// Inserts a decoded evm2 BAL into the cache.
289    pub fn insert_bal(&self, block_hash: B256, bal: DecodedBal<Arc<EvmBal>>) {
290        let _ =
291            self.to_service.send(CacheAction::InsertBal { block_hash, bal: CachedBal::new(bal) });
292    }
293}
294/// Thrown when the cache service task dropped.
295#[derive(Debug, thiserror::Error)]
296#[error("cache service task stopped")]
297pub struct CacheServiceUnavailable;
298
299impl From<CacheServiceUnavailable> for ProviderError {
300    fn from(err: CacheServiceUnavailable) -> Self {
301        Self::other(err)
302    }
303}
304
305/// A task that manages caches for data required by the `eth` rpc implementation.
306///
307/// It provides a caching layer on top of the given
308/// [`StateProvider`](reth_storage_api::StateProvider) and keeps data fetched via the provider in
309/// memory in an LRU cache. If the requested data is missing in the cache it is fetched and inserted
310/// into the cache afterwards. While fetching data from disk is sync, this service is async since
311/// requests and data is shared via channels.
312///
313/// This type is an endless future that listens for incoming messages from the user facing
314/// [`EthStateCache`] via a channel. If the requested data is not cached then it spawns a new task
315/// that does the IO and sends the result back to it. This way the caching service only
316/// handles messages and does LRU lookups and never blocking IO.
317///
318/// Caution: The channel for the data is _unbounded_ it is assumed that this is mainly used by the
319/// `reth_rpc::EthApi` which is typically invoked by the RPC server, which already uses
320/// permits to limit concurrent requests.
321#[must_use = "Type does nothing unless spawned"]
322pub(crate) struct EthStateCacheService<Provider>
323where
324    Provider: BlockReader + BalProvider,
325{
326    /// The type used to lookup data from disk
327    provider: Provider,
328    /// The LRU cache for full blocks grouped by their block hash.
329    full_block_cache: BlockLruCache<Provider::Block>,
330    /// The LRU cache for block receipts grouped by the block hash.
331    receipts_cache: ReceiptsLruCache<Provider::Receipt>,
332    /// The LRU cache for evm2 BALs grouped by the block hash.
333    bal_cache: BalLruCache,
334    /// Maximum time without a cache hit. Zero disables idle eviction.
335    idle_timeout: Duration,
336    /// Reclaims idle entries even when there are no requests or new blocks.
337    eviction_interval: Interval,
338    /// Whether an idle sweep has more entries to remove in a subsequent poll.
339    eviction_pending: bool,
340    /// Sender half of the action channel.
341    action_tx: UnboundedSender<CacheAction<Provider::Block, Provider::Receipt>>,
342    /// Receiver half of the action channel.
343    action_rx: UnboundedReceiverStream<CacheAction<Provider::Block, Provider::Receipt>>,
344    /// The type that's used to spawn tasks that do the actual work
345    action_task_spawner: Runtime,
346    /// Rate limiter for spawned fetch tasks.
347    ///
348    /// This restricts the max concurrent fetch tasks at the same time.
349    rate_limiter: Arc<Semaphore>,
350    /// Cache misses waiting for a database request slot.
351    ///
352    /// A request is only moved to the blocking pool after it acquires a permit. This prevents
353    /// cache misses that are waiting for a slot from occupying Tokio blocking threads.
354    pending_fetches: VecDeque<CacheFetch>,
355    /// LRU index mapping transaction hashes to their block hash and index within the block.
356    tx_hash_index: LruMap<TxHash, (B256, usize), FbBuildHasher<32>>,
357}
358
359impl<Provider> EthStateCacheService<Provider>
360where
361    Provider: BlockReader + BalProvider + Clone + Unpin + 'static,
362{
363    fn queue_fetch(&mut self, fetch: CacheFetch) {
364        if self.pending_fetches.is_empty() &&
365            let Ok(permit) = self.rate_limiter.clone().try_acquire_owned()
366        {
367            self.spawn_fetch(fetch, permit);
368            return
369        }
370
371        self.pending_fetches.push_back(fetch);
372        self.spawn_pending_fetches();
373    }
374
375    fn spawn_pending_fetches(&mut self) {
376        while let Some(fetch) = self.pending_fetches.pop_front() {
377            let Ok(permit) = self.rate_limiter.clone().try_acquire_owned() else {
378                self.pending_fetches.push_front(fetch);
379                return
380            };
381
382            self.spawn_fetch(fetch, permit);
383        }
384    }
385
386    fn spawn_fetch(&self, fetch: CacheFetch, permit: OwnedSemaphorePermit) {
387        let provider = self.provider.clone();
388        let action_tx = self.action_tx.clone();
389
390        match fetch {
391            CacheFetch::Block(block_hash) => {
392                let mut action_sender = ActionSender::new(CacheKind::Block, block_hash, action_tx);
393                self.action_task_spawner.spawn_blocking_task(async move {
394                    let block_sender = provider
395                        .sealed_block_with_senders(
396                            BlockHashOrNumber::Hash(block_hash),
397                            TransactionVariant::WithHash,
398                        )
399                        .map(|maybe_block| maybe_block.map(Arc::new));
400                    drop(permit);
401                    action_sender.send_block(block_sender);
402                });
403            }
404            CacheFetch::Receipts(block_hash) => {
405                let mut action_sender =
406                    ActionSender::new(CacheKind::Receipt, block_hash, action_tx);
407                self.action_task_spawner.spawn_blocking_task(async move {
408                    let res = provider
409                        .receipts_by_block(block_hash.into())
410                        .map(|maybe_receipts| maybe_receipts.map(Arc::new));
411                    drop(permit);
412                    action_sender.send_receipts(res);
413                });
414            }
415            CacheFetch::Bal(block_hash) => {
416                let mut action_sender = ActionSender::new(CacheKind::Bal, block_hash, action_tx);
417                self.action_task_spawner.spawn_blocking_task(async move {
418                    let res = provider
419                        .get_bal_by_hash(block_hash)
420                        .and_then(|maybe_bal| maybe_bal.map(CachedBal::try_from_raw).transpose());
421                    drop(permit);
422                    action_sender.send_bal(res);
423                });
424            }
425        }
426    }
427
428    /// Indexes all transactions in a block by transaction hash.
429    fn index_block_transactions(&mut self, block: &RecoveredBlock<Provider::Block>) {
430        let block_hash = block.hash();
431        for (tx_idx, tx) in block.body().transactions().iter().enumerate() {
432            self.tx_hash_index.insert(*tx.tx_hash(), (block_hash, tx_idx));
433        }
434    }
435
436    /// Removes transaction index entries for a reorged block.
437    fn remove_block_transactions(&mut self, block: &RecoveredBlock<Provider::Block>) {
438        for tx in block.body().transactions() {
439            self.tx_hash_index.remove(tx.tx_hash());
440        }
441    }
442
443    fn on_new_block(
444        &mut self,
445        block_hash: B256,
446        res: ProviderResult<Option<Arc<RecoveredBlock<Provider::Block>>>>,
447        now: Instant,
448    ) {
449        if let Some(queued) = self.full_block_cache.remove(&block_hash) {
450            // send the response to queued senders
451            for tx in queued {
452                let _ = tx.send(res.clone());
453            }
454        }
455
456        // cache good block
457        if let Ok(Some(block)) = res {
458            self.full_block_cache.insert_at(block_hash, block, now);
459        }
460    }
461
462    fn on_new_receipts(
463        &mut self,
464        block_hash: B256,
465        res: ProviderResult<Option<Arc<Vec<Provider::Receipt>>>>,
466        now: Instant,
467    ) {
468        if let Some(queued) = self.receipts_cache.remove(&block_hash) {
469            // send the response to queued senders
470            for tx in queued {
471                let _ = tx.send(res.clone());
472            }
473        }
474
475        // cache good receipts
476        if let Ok(Some(receipts)) = res {
477            self.receipts_cache.insert_at(block_hash, receipts, now);
478        }
479    }
480
481    fn on_new_bal(
482        &mut self,
483        block_hash: B256,
484        res: ProviderResult<Option<CachedBal>>,
485        now: Instant,
486    ) {
487        // A local replay may finish before an older provider lookup returns.
488        if self.bal_cache.contains_key(&block_hash) {
489            return
490        }
491
492        if let Some(queued) = self.bal_cache.remove(&block_hash) {
493            for tx in queued {
494                let _ = tx.send(res.clone());
495            }
496        }
497
498        if let Ok(Some(bal)) = res {
499            self.bal_cache.insert_at(block_hash, bal, now);
500        }
501    }
502
503    fn on_reorg_block(
504        &mut self,
505        block_hash: B256,
506        res: ProviderResult<Option<Arc<RecoveredBlock<Provider::Block>>>>,
507    ) {
508        if let Some(queued) = self.full_block_cache.remove(&block_hash) {
509            // send the response to queued senders
510            for tx in queued {
511                let _ = tx.send(res.clone());
512            }
513        }
514    }
515
516    fn on_reorg_receipts(
517        &mut self,
518        block_hash: B256,
519        res: ProviderResult<Option<Arc<Vec<Provider::Receipt>>>>,
520    ) {
521        if let Some(queued) = self.receipts_cache.remove(&block_hash) {
522            // send the response to queued senders
523            for tx in queued {
524                let _ = tx.send(res.clone());
525            }
526        }
527    }
528
529    fn on_reorg_bal(&mut self, block_hash: B256, res: ProviderResult<Option<CachedBal>>) {
530        if let Some(queued) = self.bal_cache.remove(&block_hash) {
531            for tx in queued {
532                let _ = tx.send(res.clone());
533            }
534        }
535    }
536
537    /// Shrinks the queues but leaves some space for the next requests
538    fn shrink_queues(&mut self) {
539        let min_capacity = 2;
540        self.full_block_cache.shrink_to(min_capacity);
541        self.receipts_cache.shrink_to(min_capacity);
542        self.bal_cache.shrink_to(min_capacity);
543    }
544
545    fn update_cached_metrics(&mut self) {
546        self.full_block_cache.update_cached_metrics();
547        self.receipts_cache.update_cached_metrics();
548        self.bal_cache.update_cached_metrics();
549    }
550}
551
552impl<Provider> Future for EthStateCacheService<Provider>
553where
554    Provider: BlockReader + BalProvider + Clone + Unpin + 'static,
555{
556    type Output = ();
557
558    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
559        let this = self.get_mut();
560        // All inserts and hits in this poll share a clock read, preserving timestamp/LRU order.
561        let now = LazyCell::new(Instant::now);
562
563        // Poll until pending to register the next timer wakeup before draining requests.
564        while !this.idle_timeout.is_zero() && this.eviction_interval.poll_tick(cx).is_ready() {
565            this.eviction_pending = true;
566        }
567        if this.eviction_pending {
568            let now = *now;
569            this.eviction_pending = this.full_block_cache.evict_expired(now, this.idle_timeout);
570            this.eviction_pending |= this.receipts_cache.evict_expired(now, this.idle_timeout);
571            this.eviction_pending |= this.bal_cache.evict_expired(now, this.idle_timeout);
572            if this.eviction_pending {
573                // Process requests before continuing the sweep in another poll.
574                cx.waker().wake_by_ref();
575            }
576        }
577
578        loop {
579            let Poll::Ready(action) = this.action_rx.poll_next_unpin(cx) else {
580                // shrink queues if we don't have any work to do
581                this.shrink_queues();
582                // gauges only need to be accurate once the batch of messages is drained, and only
583                // caches that changed during the batch republish theirs
584                this.update_cached_metrics();
585                return Poll::Pending;
586            };
587
588            match action {
589                None => {
590                    unreachable!("can't close")
591                }
592                Some(action) => {
593                    let now = *now;
594                    match action {
595                        CacheAction::GetCachedBlock { block_hash, response_tx } => {
596                            let _ = response_tx
597                                .send(this.full_block_cache.get_at(&block_hash, now).cloned());
598                        }
599                        CacheAction::GetCachedBal { block_hash, response_tx } => {
600                            let _ =
601                                response_tx.send(this.bal_cache.get_at(&block_hash, now).cloned());
602                        }
603                        CacheAction::GetCachedBlockAndReceipts { block_hash, response_tx } => {
604                            let block = this.full_block_cache.get_at(&block_hash, now).cloned();
605                            let receipts = this.receipts_cache.get_at(&block_hash, now).cloned();
606                            let _ = response_tx.send((block, receipts));
607                        }
608                        CacheAction::GetBlockWithSenders { block_hash, response_tx } => {
609                            if let Some(block) =
610                                this.full_block_cache.get_at(&block_hash, now).cloned()
611                            {
612                                let _ = response_tx.send(Ok(Some(block)));
613                                continue
614                            }
615
616                            // block is not in the cache, request it if this is the first consumer
617                            if this.full_block_cache.queue(block_hash, response_tx) {
618                                this.queue_fetch(CacheFetch::Block(block_hash));
619                            }
620                        }
621                        CacheAction::GetReceipts { block_hash, response_tx } => {
622                            // check if block is cached
623                            if let Some(receipts) =
624                                this.receipts_cache.get_at(&block_hash, now).cloned()
625                            {
626                                let _ = response_tx.send(Ok(Some(receipts)));
627                                continue
628                            }
629
630                            // block is not in the cache, request it if this is the first consumer
631                            if this.receipts_cache.queue(block_hash, response_tx) {
632                                this.queue_fetch(CacheFetch::Receipts(block_hash));
633                            }
634                        }
635                        CacheAction::GetBal { block_hash, response_tx } => {
636                            if let Some(bal) = this.bal_cache.get_at(&block_hash, now).cloned() {
637                                let _ = response_tx.send(Ok(Some(bal)));
638                                continue
639                            }
640
641                            if this.bal_cache.queue(block_hash, response_tx) {
642                                this.queue_fetch(CacheFetch::Bal(block_hash));
643                            }
644                        }
645                        CacheAction::ReceiptsResult { block_hash, res } => {
646                            this.on_new_receipts(block_hash, res, now);
647                        }
648                        CacheAction::BalResult { block_hash, res } => {
649                            this.on_new_bal(block_hash, res, now);
650                        }
651                        CacheAction::InsertBal { block_hash, bal } => {
652                            this.on_new_bal(block_hash, Ok(Some(bal)), now);
653                        }
654                        CacheAction::BlockWithSendersResult { block_hash, res } => {
655                            this.on_new_block(block_hash, res, now);
656                        }
657                        CacheAction::CacheNewCanonicalChain { chain_change } => {
658                            for block in chain_change.blocks {
659                                // Index transactions before caching the block
660                                this.index_block_transactions(&block);
661                                this.on_new_block(block.hash(), Ok(Some(block)), now);
662                            }
663
664                            for block_receipts in chain_change.receipts {
665                                this.on_new_receipts(
666                                    block_receipts.block_hash,
667                                    Ok(Some(block_receipts.receipts)),
668                                    now,
669                                );
670                            }
671
672                            for (block_hash, bal) in chain_change.bals {
673                                this.on_new_bal(
674                                    block_hash,
675                                    Ok(Some(CachedBal::from_shared(bal))),
676                                    now,
677                                );
678                            }
679                        }
680                        CacheAction::RemoveReorgedChain { chain_change } => {
681                            for block in chain_change.blocks {
682                                let block_hash = block.hash();
683                                // Remove transaction index entries for reorged blocks
684                                this.remove_block_transactions(&block);
685                                this.on_reorg_block(block_hash, Ok(Some(block)));
686                                this.on_reorg_bal(block_hash, Ok(None));
687                            }
688
689                            for block_receipts in chain_change.receipts {
690                                this.on_reorg_receipts(
691                                    block_receipts.block_hash,
692                                    Ok(Some(block_receipts.receipts)),
693                                );
694                            }
695                        }
696                        CacheAction::GetTransactionByHash { tx_hash, response_tx } => {
697                            let result =
698                                this.tx_hash_index.get(&tx_hash).and_then(|(block_hash, idx)| {
699                                    let block =
700                                        this.full_block_cache.get_at(block_hash, now).cloned()?;
701                                    let receipts =
702                                        this.receipts_cache.get_at(block_hash, now).cloned();
703                                    Some(CachedTransaction::new(block, *idx, receipts))
704                                });
705                            let _ = response_tx.send(result);
706                        }
707                    };
708                    this.spawn_pending_fetches();
709                }
710            }
711        }
712    }
713}
714
715/// All message variants sent through the channel
716enum CacheAction<B: Block, R> {
717    GetBlockWithSenders {
718        block_hash: B256,
719        response_tx: BlockWithSendersResponseSender<B>,
720    },
721    GetReceipts {
722        block_hash: B256,
723        response_tx: ReceiptsResponseSender<R>,
724    },
725    GetBal {
726        block_hash: B256,
727        response_tx: BalResponseSender,
728    },
729    GetCachedBlock {
730        block_hash: B256,
731        response_tx: CachedBlockResponseSender<B>,
732    },
733    GetCachedBal {
734        block_hash: B256,
735        response_tx: CachedBalResponseSender,
736    },
737    GetCachedBlockAndReceipts {
738        block_hash: B256,
739        response_tx: CachedBlockAndReceiptsResponseSender<B, R>,
740    },
741    BlockWithSendersResult {
742        block_hash: B256,
743        res: ProviderResult<Option<Arc<RecoveredBlock<B>>>>,
744    },
745    ReceiptsResult {
746        block_hash: B256,
747        res: ProviderResult<Option<Arc<Vec<R>>>>,
748    },
749    BalResult {
750        block_hash: B256,
751        res: ProviderResult<Option<CachedBal>>,
752    },
753    InsertBal {
754        block_hash: B256,
755        bal: CachedBal,
756    },
757    CacheNewCanonicalChain {
758        chain_change: ChainChange<B, R>,
759    },
760    RemoveReorgedChain {
761        chain_change: ChainChange<B, R>,
762    },
763    /// Look up a transaction's cached data by its hash
764    GetTransactionByHash {
765        tx_hash: TxHash,
766        response_tx: TransactionHashResponseSender<B, R>,
767    },
768}
769
770struct BlockReceipts<R> {
771    block_hash: B256,
772    receipts: Arc<Vec<R>>,
773}
774
775/// A change of the canonical chain
776struct ChainChange<B: Block, R> {
777    blocks: Vec<Arc<RecoveredBlock<B>>>,
778    receipts: Vec<BlockReceipts<R>>,
779    bals: Vec<(B256, Arc<DecodedEvmBal>)>,
780}
781
782impl<B: Block, R: Clone> ChainChange<B, R> {
783    fn new<N>(chain: Arc<Chain<N>>) -> Self
784    where
785        N: NodePrimitives<Block = B, Receipt = R>,
786    {
787        let (blocks, receipts): (Vec<_>, Vec<_>) = chain
788            .blocks_and_receipts()
789            .map(|(block, receipts)| {
790                let block_receipts = BlockReceipts {
791                    block_hash: block.hash(),
792                    receipts: Arc::new(receipts.clone()),
793                };
794                (Arc::clone(block), block_receipts)
795            })
796            .unzip();
797        // Blocks without an attached BAL are absent here; their BAL is fetched from the store on
798        // demand.
799        let bals =
800            chain.blocks_and_bals().map(|(block, bal)| (block.hash(), Arc::clone(bal))).collect();
801        Self { blocks, receipts, bals }
802    }
803}
804
805/// Identifier for the caches.
806#[derive(Copy, Clone, Debug)]
807enum CacheKind {
808    Block,
809    Receipt,
810    Bal,
811}
812
813#[derive(Copy, Clone, Debug)]
814enum CacheFetch {
815    Block(B256),
816    Receipts(B256),
817    Bal(B256),
818}
819
820/// Drop aware sender struct that ensures a response is always emitted even if the db task panics
821/// before a result could be sent.
822///
823/// This type wraps a sender and in case the sender is still present on drop emit an error response.
824#[derive(Debug)]
825struct ActionSender<B: Block, R: Send + Sync> {
826    kind: CacheKind,
827    blockhash: B256,
828    tx: Option<UnboundedSender<CacheAction<B, R>>>,
829}
830
831impl<R: Send + Sync, B: Block> ActionSender<B, R> {
832    const fn new(kind: CacheKind, blockhash: B256, tx: UnboundedSender<CacheAction<B, R>>) -> Self {
833        Self { kind, blockhash, tx: Some(tx) }
834    }
835
836    fn send_block(&mut self, block_sender: Result<Option<Arc<RecoveredBlock<B>>>, ProviderError>) {
837        if let Some(tx) = self.tx.take() {
838            let _ = tx.send(CacheAction::BlockWithSendersResult {
839                block_hash: self.blockhash,
840                res: block_sender,
841            });
842        }
843    }
844
845    fn send_receipts(&mut self, receipts: Result<Option<Arc<Vec<R>>>, ProviderError>) {
846        if let Some(tx) = self.tx.take() {
847            let _ =
848                tx.send(CacheAction::ReceiptsResult { block_hash: self.blockhash, res: receipts });
849        }
850    }
851
852    fn send_bal(&mut self, bal: Result<Option<CachedBal>, ProviderError>) {
853        if let Some(tx) = self.tx.take() {
854            let _ = tx.send(CacheAction::BalResult { block_hash: self.blockhash, res: bal });
855        }
856    }
857}
858impl<R: Send + Sync, B: Block> Drop for ActionSender<B, R> {
859    fn drop(&mut self) {
860        if let Some(tx) = self.tx.take() {
861            let msg = match self.kind {
862                CacheKind::Block => CacheAction::BlockWithSendersResult {
863                    block_hash: self.blockhash,
864                    res: Err(CacheServiceUnavailable.into()),
865                },
866                CacheKind::Receipt => CacheAction::ReceiptsResult {
867                    block_hash: self.blockhash,
868                    res: Err(CacheServiceUnavailable.into()),
869                },
870                CacheKind::Bal => CacheAction::BalResult {
871                    block_hash: self.blockhash,
872                    res: Err(CacheServiceUnavailable.into()),
873                },
874            };
875            let _ = tx.send(msg);
876        }
877    }
878}
879
880/// Awaits for new chain events and directly inserts them into the cache so they're available
881/// immediately before they need to be fetched from disk.
882///
883/// Reorged blocks are removed from the cache.
884pub async fn cache_new_blocks_task<St, N: NodePrimitives>(
885    eth_state_cache: EthStateCache<N>,
886    mut events: St,
887) where
888    St: Stream<Item = CanonStateNotification<N>> + Unpin + 'static,
889{
890    while let Some(event) = events.next().await {
891        if let Some(reverted) = event.reverted() {
892            let chain_change = ChainChange::new(reverted);
893
894            let _ =
895                eth_state_cache.to_service.send(CacheAction::RemoveReorgedChain { chain_change });
896        }
897
898        let chain_change = ChainChange::new(event.committed());
899
900        let _ =
901            eth_state_cache.to_service.send(CacheAction::CacheNewCanonicalChain { chain_change });
902    }
903}
904
905/// Cached decoded evm2 BAL.
906#[derive(Clone, Debug)]
907pub(crate) struct CachedBal(Arc<DecodedEvmBal>);
908
909impl CachedBal {
910    /// Creates a cached evm2 BAL from an owned decoded BAL.
911    #[inline]
912    fn new(bal: DecodedEvmBal) -> Self {
913        Self(Arc::new(bal))
914    }
915
916    /// Creates a cached evm2 BAL from a BAL that is already shared, for example one prepared
917    /// during block validation.
918    #[inline]
919    const fn from_shared(bal: Arc<DecodedEvmBal>) -> Self {
920        Self(bal)
921    }
922
923    /// Decodes raw BAL bytes into the representation used by evm2.
924    fn try_from_raw(raw: Bytes) -> ProviderResult<Self> {
925        DecodedBal::from_rlp_bytes(raw)
926            .map_err(Into::into)
927            .and_then(|decoded| {
928                decoded.try_map(|bal| {
929                    EvmBal::try_from(Vec::from(bal)).map(Arc::new).map_err(ProviderError::other)
930                })
931            })
932            .map(Self::new)
933    }
934}
935
936impl InMemorySize for CachedBal {
937    fn size(&self) -> usize {
938        core::mem::size_of::<Self>() + decoded_bal_size(&self.0)
939    }
940}
941
942fn decoded_bal_size(bal: &DecodedEvmBal) -> usize {
943    core::mem::size_of::<DecodedEvmBal>() + bal.as_raw().len() + evm_bal_size(bal.as_bal())
944}
945
946fn evm_bal_size(bal: &EvmBal) -> usize {
947    core::mem::size_of::<EvmBal>() +
948        bal.accounts.capacity() * core::mem::size_of::<(Address, EvmAccountBal)>() +
949        bal.accounts.values().map(evm_account_bal_heap_size).sum::<usize>()
950}
951
952fn evm_account_bal_heap_size(account: &EvmAccountBal) -> usize {
953    evm_account_info_bal_heap_size(&account.account_info) +
954        evm_storage_bal_heap_size(&account.storage)
955}
956
957fn evm_account_info_bal_heap_size(account_info: &EvmAccountInfoBal) -> usize {
958    evm_bal_changes_heap_size(&account_info.nonce, |_| 0) +
959        evm_bal_changes_heap_size(&account_info.balance, |_| 0) +
960        evm_bal_changes_heap_size(&account_info.code, evm_code_change_heap_size)
961}
962
963fn evm_storage_bal_heap_size(storage: &EvmStorageBal) -> usize {
964    storage.storage.capacity() * core::mem::size_of::<(U256, EvmBalChanges<StorageChange>)>() +
965        storage
966            .storage
967            .values()
968            .map(|changes| evm_bal_changes_heap_size(changes, |_| 0))
969            .sum::<usize>()
970}
971
972fn evm_bal_changes_heap_size<T, F>(changes: &EvmBalChanges<T>, mut item_heap_size: F) -> usize
973where
974    T: EvmBalChange,
975    F: FnMut(&T) -> usize,
976{
977    changes.changes.capacity() * core::mem::size_of::<T>() +
978        changes.changes.iter().map(&mut item_heap_size).sum::<usize>()
979}
980
981fn evm_code_change_heap_size(change: &EvmBalCodeChange) -> usize {
982    bytecode_heap_size(&change.code.1)
983}
984
985fn bytecode_heap_size(bytecode: &Bytecode) -> usize {
986    bytecode.original_bytes().len()
987}
988
989#[cfg(test)]
990mod tests {
991    use super::*;
992    use alloy_consensus::{transaction::TransactionMeta, Header};
993    use alloy_eip7928::BlockAccessIndex;
994    use alloy_eips::{BlockHashOrNumber, NumHash};
995    use alloy_primitives::{
996        Address, BlockHash, BlockNumber, Bytes, Signature, TxHash, TxNumber, U256,
997    };
998    use core::ops::{RangeBounds, RangeInclusive};
999    use reth_db_models::StoredBlockBodyIndices;
1000    use reth_ethereum_primitives::{
1001        Block, BlockBody, EthPrimitives, Receipt, Transaction, TransactionSigned,
1002    };
1003    use reth_execution_types::{ExecutionOutcome, RecoveredBlockAndExecutionOutput};
1004    use reth_primitives_traits::{RecoveredBlock, SealedHeader};
1005    use reth_storage_api::{
1006        noop::NoopProvider, BalProvider, BalStore, BalStoreHandle, BlockBodyIndicesProvider,
1007        BlockHashReader, BlockNumReader, BlockReader, BlockSource, HeaderProvider, ReceiptProvider,
1008        TransactionVariant, TransactionsProvider,
1009    };
1010    use std::{
1011        sync::{
1012            atomic::{AtomicUsize, Ordering},
1013            mpsc::{self, Receiver, SyncSender},
1014            Mutex,
1015        },
1016        task::{Wake, Waker},
1017        thread,
1018        time::Duration,
1019    };
1020
1021    #[derive(Default)]
1022    struct WakeCounter(AtomicUsize);
1023
1024    impl Wake for WakeCounter {
1025        fn wake(self: Arc<Self>) {
1026            self.0.fetch_add(1, Ordering::Relaxed);
1027        }
1028    }
1029
1030    fn test_service() -> EthStateCacheService<NoopProvider> {
1031        let (_cache, service) = EthStateCache::<EthPrimitives>::create(
1032            NoopProvider::default(),
1033            Runtime::test(),
1034            EthStateCacheConfig {
1035                max_blocks: 4,
1036                max_receipts: 4,
1037                max_bals: 4,
1038                cache_computed_bals: false,
1039                prewarm_bals: None,
1040                max_concurrent_db_requests: 1,
1041                max_cached_tx_hashes: 16,
1042                ..Default::default()
1043            },
1044        );
1045        service
1046    }
1047
1048    fn test_decoded_evm_bal() -> DecodedBal<Arc<EvmBal>> {
1049        DecodedBal::new(Arc::new(EvmBal::default()), Bytes::from_static(&[0xc0]))
1050    }
1051
1052    fn test_block() -> RecoveredBlock<Block> {
1053        RecoveredBlock::new_unhashed(
1054            Block {
1055                header: Header { number: 1, ..Default::default() },
1056                body: BlockBody {
1057                    transactions: vec![TransactionSigned::new_unhashed(
1058                        Transaction::Legacy(Default::default()),
1059                        Signature::test_signature(),
1060                    )],
1061                    ..Default::default()
1062                },
1063            },
1064            vec![Address::ZERO],
1065        )
1066    }
1067
1068    #[tokio::test(start_paused = true)]
1069    async fn idle_timer_continues_waking_after_its_first_tick() {
1070        let (cache, mut service) = EthStateCache::<EthPrimitives>::create(
1071            NoopProvider::default(),
1072            Runtime::test(),
1073            EthStateCacheConfig { idle_timeout: Duration::from_secs(10), ..Default::default() },
1074        );
1075        tokio::time::advance(Duration::from_secs(3)).await;
1076        let block = Arc::new(test_block());
1077        let hash = block.hash();
1078        let receipts = Arc::new(vec![]);
1079        let bal = CachedBal::new(test_decoded_evm_bal());
1080        let retained_block = Arc::downgrade(&block);
1081        let retained_receipts = Arc::downgrade(&receipts);
1082        let retained_bal = Arc::downgrade(&bal.0);
1083        service.on_new_block(hash, Ok(Some(block)), Instant::now());
1084        service.on_new_receipts(hash, Ok(Some(receipts)), Instant::now());
1085        service.on_new_bal(hash, Ok(Some(bal)), Instant::now());
1086        let (response_tx, response_rx) = oneshot::channel();
1087        service.full_block_cache.queue(hash, response_tx);
1088        let service_task = tokio::spawn(service);
1089        assert!(cache.get_maybe_block(hash).await.unwrap().is_some());
1090
1091        tokio::time::advance(Duration::from_secs(7)).await;
1092        tokio::task::yield_now().await;
1093        assert!(retained_block.upgrade().is_some());
1094        assert!(retained_receipts.upgrade().is_some());
1095        assert!(retained_bal.upgrade().is_some());
1096        // No more cache actions: only a later timer tick can release these payloads.
1097        tokio::time::advance(Duration::from_secs(11)).await;
1098        tokio::task::yield_now().await;
1099        let expired = retained_block.upgrade().is_none() &&
1100            retained_receipts.upgrade().is_none() &&
1101            retained_bal.upgrade().is_none();
1102
1103        // Expiration must leave pending consumers available for the next result.
1104        cache
1105            .to_service
1106            .send(CacheAction::BlockWithSendersResult {
1107                block_hash: hash,
1108                res: Ok(Some(Arc::new(test_block()))),
1109            })
1110            .unwrap();
1111        let response = response_rx.await.unwrap();
1112        service_task.abort();
1113        assert!(expired, "idle service did not register its next timer wakeup");
1114        assert!(response.unwrap().is_some());
1115    }
1116
1117    #[tokio::test(start_paused = true)]
1118    async fn idle_eviction_yields_between_batches_and_serves_requests() {
1119        let (cache, mut service) = EthStateCache::<EthPrimitives>::create(
1120            NoopProvider::default(),
1121            Runtime::test(),
1122            EthStateCacheConfig { idle_timeout: Duration::from_secs(10), ..Default::default() },
1123        );
1124        let mut retained = Vec::new();
1125        for key in 0..12 {
1126            let hash = B256::with_last_byte(key);
1127            let block = Arc::new(test_block());
1128            let receipts = Arc::new(vec![]);
1129            let bal = CachedBal::new(test_decoded_evm_bal());
1130            retained.push((
1131                Arc::downgrade(&block),
1132                Arc::downgrade(&receipts),
1133                Arc::downgrade(&bal.0),
1134            ));
1135            service.on_new_block(hash, Ok(Some(block)), Instant::now());
1136            service.on_new_receipts(hash, Ok(Some(receipts)), Instant::now());
1137            service.on_new_bal(hash, Ok(Some(bal)), Instant::now());
1138        }
1139        let (response_tx, mut response_rx) = oneshot::channel();
1140        cache
1141            .to_service
1142            .send(CacheAction::GetCachedBlock { block_hash: B256::repeat_byte(0xff), response_tx })
1143            .unwrap();
1144        tokio::time::advance(Duration::from_secs(10)).await;
1145
1146        let wakes = Arc::new(WakeCounter::default());
1147        let waker = Waker::from(wakes.clone());
1148        let mut cx = Context::from_waker(&waker);
1149        for (remaining, expected_wakes) in [(7, 1), (2, 2), (0, 2)] {
1150            assert!(Pin::new(&mut service).poll(&mut cx).is_pending());
1151            assert_eq!(wakes.0.load(Ordering::Relaxed), expected_wakes);
1152            assert_eq!(
1153                retained.iter().filter(|(block, _, _)| block.strong_count() > 0).count(),
1154                remaining
1155            );
1156            assert_eq!(
1157                retained.iter().filter(|(_, receipts, _)| receipts.strong_count() > 0).count(),
1158                remaining
1159            );
1160            assert_eq!(
1161                retained.iter().filter(|(_, _, bal)| bal.strong_count() > 0).count(),
1162                remaining
1163            );
1164            if remaining == 7 {
1165                assert!(response_rx.try_recv().unwrap().is_none());
1166            }
1167        }
1168    }
1169
1170    #[tokio::test(start_paused = true)]
1171    async fn disabled_idle_timeout_does_not_schedule_cleanup() {
1172        let (_cache, mut service) = EthStateCache::<EthPrimitives>::create(
1173            NoopProvider::default(),
1174            Runtime::test(),
1175            EthStateCacheConfig { idle_timeout: Duration::ZERO, ..Default::default() },
1176        );
1177        let wakes = Arc::new(WakeCounter::default());
1178        let waker = Waker::from(wakes.clone());
1179        let mut cx = Context::from_waker(&waker);
1180        assert!(Pin::new(&mut service).poll(&mut cx).is_pending());
1181
1182        tokio::time::advance(Duration::from_secs(10)).await;
1183        assert_eq!(wakes.0.load(Ordering::Relaxed), 0);
1184    }
1185
1186    #[test]
1187    fn oversized_results_reach_queued_consumers() {
1188        let block = Arc::new(test_block());
1189        let receipts = Arc::new(vec![Receipt::default()]);
1190        let bal = CachedBal::new(test_decoded_evm_bal());
1191        let hash = block.hash();
1192        let (_cache, mut service) = EthStateCache::<EthPrimitives>::create(
1193            NoopProvider::default(),
1194            Runtime::test(),
1195            EthStateCacheConfig {
1196                max_blocks_bytes: block.size() - 1,
1197                max_receipts_bytes: receipts.size() - 1,
1198                max_bals_bytes: bal.size() - 1,
1199                ..Default::default()
1200            },
1201        );
1202        let (block_tx, mut block_rx) = oneshot::channel();
1203        let (receipts_tx, mut receipts_rx) = oneshot::channel();
1204        let (bal_tx, mut bal_rx) = oneshot::channel();
1205        service.full_block_cache.queue(hash, block_tx);
1206        service.receipts_cache.queue(hash, receipts_tx);
1207        service.bal_cache.queue(hash, bal_tx);
1208
1209        service.on_new_block(hash, Ok(Some(block.clone())), Instant::now());
1210        service.on_new_receipts(hash, Ok(Some(receipts.clone())), Instant::now());
1211        service.on_new_bal(hash, Ok(Some(bal.clone())), Instant::now());
1212
1213        assert!(Arc::ptr_eq(&block_rx.try_recv().unwrap().unwrap().unwrap(), &block));
1214        assert!(Arc::ptr_eq(&receipts_rx.try_recv().unwrap().unwrap().unwrap(), &receipts));
1215        assert!(Arc::ptr_eq(&bal_rx.try_recv().unwrap().unwrap().unwrap().0, &bal.0));
1216        assert!(service.full_block_cache.get(&hash).is_none());
1217        assert!(service.receipts_cache.get(&hash).is_none());
1218        assert!(service.bal_cache.get(&hash).is_none());
1219    }
1220
1221    #[tokio::test(start_paused = true)]
1222    async fn cached_transaction_lookup_renews_idle_timeout() {
1223        let (cache, mut service) = EthStateCache::<EthPrimitives>::create(
1224            NoopProvider::default(),
1225            Runtime::test(),
1226            EthStateCacheConfig { idle_timeout: Duration::from_secs(10), ..Default::default() },
1227        );
1228        let block = Arc::new(test_block());
1229        let hash = block.hash();
1230        let tx_hash = *block.body().transactions().next().unwrap().tx_hash();
1231        service.index_block_transactions(&block);
1232        service.on_new_block(hash, Ok(Some(block)), Instant::now());
1233        service.on_new_receipts(hash, Ok(Some(Arc::new(vec![Receipt::default()]))), Instant::now());
1234
1235        for delay in [6, 6, 11] {
1236            tokio::time::advance(Duration::from_secs(delay)).await;
1237            let request = cache.get_transaction_by_hash(tx_hash);
1238            futures::pin_mut!(request);
1239            assert!(futures::poll!(&mut request).is_pending());
1240            assert!(futures::poll!(&mut service).is_pending());
1241            let result = request.await;
1242            if delay < 10 {
1243                let result = result.expect("transaction is still cached after a renewed hit");
1244                assert_eq!(result.block.hash(), hash);
1245                assert!(result.receipts.is_some());
1246            } else {
1247                assert!(result.is_none());
1248            }
1249        }
1250    }
1251
1252    #[test]
1253    fn reorg_removes_tx_hash_index_entries_unconditionally() {
1254        let mut service = test_service();
1255        let block = test_block();
1256        let tx_hash = *block.body().transactions().next().expect("test transaction").tx_hash();
1257
1258        service.tx_hash_index.insert(tx_hash, (B256::repeat_byte(0x33), 0));
1259
1260        service.remove_block_transactions(&block);
1261
1262        assert!(service.tx_hash_index.get(&tx_hash).is_none());
1263    }
1264
1265    #[test]
1266    fn reorg_evicts_cached_bal() {
1267        let mut service = test_service();
1268        let block_hash = B256::repeat_byte(0x44);
1269
1270        assert!(service.bal_cache.insert(block_hash, CachedBal::new(test_decoded_evm_bal())));
1271        assert!(service.bal_cache.get(&block_hash).is_some());
1272
1273        service.on_reorg_bal(block_hash, Ok(None));
1274
1275        assert!(service.bal_cache.get(&block_hash).is_none());
1276    }
1277
1278    #[test]
1279    fn late_provider_miss_preserves_generated_bal() {
1280        let mut service = test_service();
1281        let hash = B256::repeat_byte(0x69);
1282        let (tx, mut rx) = oneshot::channel();
1283        assert!(service.bal_cache.queue(hash, tx));
1284        service.on_new_bal(hash, Ok(Some(CachedBal::new(test_decoded_evm_bal()))), Instant::now());
1285        assert!(rx.try_recv().unwrap().unwrap().is_some());
1286        service.on_new_bal(hash, Ok(None), Instant::now());
1287        assert!(service.bal_cache.contains_key(&hash));
1288    }
1289
1290    #[tokio::test]
1291    async fn cache_misses_wait_in_service_queue_for_db_slot() {
1292        let (started_tx, started_rx) = mpsc::sync_channel(1);
1293        let (release_tx, release_rx) = mpsc::sync_channel(1);
1294        let (_cache, mut service) = EthStateCache::<EthPrimitives>::create(
1295            TestBalProvider::new_blocking(started_tx, release_rx),
1296            Runtime::test(),
1297            EthStateCacheConfig { max_concurrent_db_requests: 1, ..Default::default() },
1298        );
1299        let permit = service.rate_limiter.clone().try_acquire_owned().unwrap();
1300
1301        service.queue_fetch(CacheFetch::Bal(B256::repeat_byte(0x01)));
1302        service.queue_fetch(CacheFetch::Bal(B256::repeat_byte(0x02)));
1303
1304        assert_eq!(service.pending_fetches.len(), 2);
1305
1306        drop(permit);
1307        service.spawn_pending_fetches();
1308
1309        started_rx.recv_timeout(Duration::from_secs(1)).expect("first fetch started");
1310        assert_eq!(service.pending_fetches.len(), 1);
1311        release_tx.send(()).unwrap();
1312    }
1313
1314    #[test]
1315    fn pending_cache_miss_does_not_block_unrelated_blocking_work() {
1316        let (started_tx, started_rx) = mpsc::sync_channel(1);
1317        let (release_tx, release_rx) = mpsc::sync_channel(1);
1318        let (unrelated_tx, unrelated_rx) = mpsc::sync_channel(1);
1319
1320        let test = thread::spawn(move || {
1321            let runtime = tokio::runtime::Builder::new_multi_thread()
1322                .worker_threads(1)
1323                .max_blocking_threads(1)
1324                .enable_all()
1325                .build()
1326                .unwrap();
1327
1328            runtime.block_on(async move {
1329                let provider = TestBalProvider::new_blocking(started_tx, release_rx);
1330                let (_cache, mut service) = EthStateCache::<EthPrimitives>::create(
1331                    provider,
1332                    Runtime::test(),
1333                    EthStateCacheConfig {
1334                        max_blocks: 0,
1335                        max_receipts: 0,
1336                        max_bals: 0,
1337                        cache_computed_bals: false,
1338                        prewarm_bals: None,
1339                        max_concurrent_db_requests: 1,
1340                        max_cached_tx_hashes: 0,
1341                        ..Default::default()
1342                    },
1343                );
1344
1345                service.queue_fetch(CacheFetch::Bal(B256::repeat_byte(0x01)));
1346                started_rx.recv_timeout(Duration::from_secs(1)).expect("first fetch started");
1347
1348                service.queue_fetch(CacheFetch::Bal(B256::repeat_byte(0x02)));
1349                assert_eq!(service.pending_fetches.len(), 1);
1350
1351                tokio::task::spawn_blocking(move || unrelated_tx.send(()).unwrap());
1352                release_tx.send(()).unwrap();
1353
1354                unrelated_rx
1355                    .recv_timeout(Duration::from_secs(1))
1356                    .expect("unrelated blocking work was not starved");
1357            });
1358        });
1359
1360        test.join().unwrap();
1361    }
1362
1363    #[test]
1364    fn reorg_forwards_bal_to_queued_requests() {
1365        let mut service = test_service();
1366        let block_hash = B256::repeat_byte(0x55);
1367        let (response_tx, mut response_rx) = oneshot::channel();
1368        let bal = CachedBal::new(test_decoded_evm_bal());
1369
1370        assert!(service.bal_cache.queue(block_hash, response_tx));
1371
1372        service.on_reorg_bal(block_hash, Ok(Some(bal)));
1373
1374        let bal = response_rx.try_recv().expect("queued BAL response").expect("BAL result");
1375
1376        assert!(bal.is_some());
1377    }
1378
1379    #[test]
1380    fn cached_evm_bal_size_accounts_for_nested_allocations() {
1381        let index = BlockAccessIndex::new(1);
1382        let mut account = EvmAccountBal::default();
1383        account.account_info.nonce.changes.push(alloy_eip7928::NonceChange::new(index, 1));
1384        account
1385            .account_info
1386            .balance
1387            .changes
1388            .push(alloy_eip7928::BalanceChange::new(index, U256::from(1)));
1389        account.account_info.code.changes.push(EvmBalCodeChange::new(
1390            index,
1391            (B256::repeat_byte(0xaa), Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00]))),
1392        ));
1393        account.storage.storage.insert(
1394            U256::from(1),
1395            EvmBalChanges::new(vec![alloy_eip7928::StorageChange::new(index, U256::from(2))]),
1396        );
1397
1398        let mut bal = EvmBal::default();
1399        bal.accounts.insert(Address::ZERO, account);
1400
1401        let raw = Bytes::from_static(&[0xc0, 0x01, 0x02]);
1402        let previous_estimate = core::mem::size_of::<CachedBal>() +
1403            core::mem::size_of::<DecodedBal<Arc<EvmBal>>>() +
1404            raw.len() +
1405            core::mem::size_of::<EvmBal>();
1406        assert!(CachedBal::new(DecodedBal::new(Arc::new(bal), raw)).size() > previous_estimate);
1407    }
1408
1409    #[tokio::test]
1410    async fn get_bal_only_caches_results_within_budget() {
1411        for (max_bals_bytes, expected_fetches) in [(usize::MAX, 1), (1, 2), (0, 2)] {
1412            let fetches = Arc::new(AtomicUsize::default());
1413            let cache = EthStateCache::<EthPrimitives>::spawn_with(
1414                TestBalProvider::new(fetches.clone()),
1415                EthStateCacheConfig {
1416                    max_blocks: 0,
1417                    max_receipts: 0,
1418                    max_bals: 4,
1419                    max_bals_bytes,
1420                    cache_computed_bals: false,
1421                    prewarm_bals: None,
1422                    max_concurrent_db_requests: 1,
1423                    max_cached_tx_hashes: 0,
1424                    ..Default::default()
1425                },
1426                Runtime::test(),
1427            );
1428            let block_hash = B256::repeat_byte(0x66);
1429
1430            assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1431            assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1432
1433            assert_eq!(fetches.load(Ordering::SeqCst), expected_fetches);
1434        }
1435    }
1436
1437    #[tokio::test]
1438    async fn get_recovered_block_and_maybe_bal_does_not_fetch_bal() {
1439        let bal_fetches = Arc::new(AtomicUsize::default());
1440        let block = test_block();
1441        let block_hash = block.hash();
1442        let provider = TestBalProvider::new(bal_fetches.clone()).with_block(block);
1443        let cache = EthStateCache::<EthPrimitives>::spawn_with(
1444            provider,
1445            EthStateCacheConfig {
1446                max_blocks: 4,
1447                max_receipts: 0,
1448                max_bals: 4,
1449                cache_computed_bals: false,
1450                prewarm_bals: None,
1451                max_concurrent_db_requests: 1,
1452                max_cached_tx_hashes: 0,
1453                ..Default::default()
1454            },
1455            Runtime::test(),
1456        );
1457
1458        let (returned_block, bal) = cache
1459            .get_recovered_block_and_maybe_bal(block_hash)
1460            .await
1461            .unwrap()
1462            .expect("block exists");
1463        assert_eq!(returned_block.hash(), block_hash);
1464        assert!(bal.is_none());
1465        assert_eq!(bal_fetches.load(Ordering::SeqCst), 0);
1466
1467        assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1468
1469        let (_, bal) = cache
1470            .get_recovered_block_and_maybe_bal(block_hash)
1471            .await
1472            .unwrap()
1473            .expect("block exists");
1474        assert!(bal.is_some());
1475        assert_eq!(bal_fetches.load(Ordering::SeqCst), 1);
1476    }
1477
1478    #[tokio::test]
1479    async fn insert_bal_populates_cache_without_provider_fetch() {
1480        let fetches = Arc::new(AtomicUsize::default());
1481        let provider = TestBalProvider::new(fetches.clone());
1482        let cache = EthStateCache::<EthPrimitives>::spawn_with(
1483            provider,
1484            EthStateCacheConfig { max_bals: 4, ..Default::default() },
1485            Runtime::test(),
1486        );
1487        let block_hash = B256::repeat_byte(0x68);
1488        cache.insert_bal(block_hash, test_decoded_evm_bal());
1489        assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1490        assert_eq!(fetches.load(Ordering::SeqCst), 0);
1491    }
1492
1493    #[tokio::test]
1494    async fn canonical_chain_notification_caches_prepared_bal() {
1495        let fetches = Arc::new(AtomicUsize::default());
1496        let provider = TestBalProvider::new(fetches.clone());
1497        let cache = EthStateCache::<EthPrimitives>::spawn_with(
1498            provider,
1499            EthStateCacheConfig { max_blocks: 4, max_bals: 4, ..Default::default() },
1500            Runtime::test(),
1501        );
1502
1503        let block = test_block();
1504        let block_hash = block.hash();
1505        let block_number = block.number;
1506        let mut chain: Chain<EthPrimitives> = Chain::new(
1507            [block],
1508            ExecutionOutcome::new(Default::default(), vec![vec![]], block_number, vec![]),
1509            Default::default(),
1510        );
1511        chain.insert_bal(block_number, Arc::new(test_decoded_evm_bal()));
1512
1513        cache_new_blocks_task(
1514            cache.clone(),
1515            tokio_stream::iter([CanonStateNotification::Commit { new: Arc::new(chain) }]),
1516        )
1517        .await;
1518
1519        assert!(cache.get_bal(block_hash).await.unwrap().is_some());
1520        assert_eq!(fetches.load(Ordering::SeqCst), 0);
1521    }
1522
1523    #[tokio::test]
1524    async fn concurrent_get_bal_requests_share_fetch() {
1525        let fetches = Arc::new(AtomicUsize::default());
1526        let provider = TestBalProvider::new(fetches.clone());
1527        let cache = EthStateCache::<EthPrimitives>::spawn_with(
1528            provider,
1529            EthStateCacheConfig {
1530                max_blocks: 0,
1531                max_receipts: 0,
1532                max_bals: 4,
1533                cache_computed_bals: false,
1534                prewarm_bals: None,
1535                max_concurrent_db_requests: 1,
1536                max_cached_tx_hashes: 0,
1537                ..Default::default()
1538            },
1539            Runtime::test(),
1540        );
1541        let block_hash = B256::repeat_byte(0x77);
1542
1543        let (first, second) = tokio::join!(cache.get_bal(block_hash), cache.get_bal(block_hash));
1544
1545        assert!(first.unwrap().is_some());
1546        assert!(second.unwrap().is_some());
1547        assert_eq!(fetches.load(Ordering::SeqCst), 1);
1548    }
1549
1550    #[derive(Clone, Debug, Default)]
1551    struct TestBalProvider {
1552        bal_store: BalStoreHandle,
1553        block: Option<RecoveredBlock<Block>>,
1554    }
1555
1556    impl TestBalProvider {
1557        fn new(fetches: Arc<AtomicUsize>) -> Self {
1558            Self {
1559                bal_store: BalStoreHandle::new(TestBalStore { fetches, blocking_fetch: None }),
1560                block: None,
1561            }
1562        }
1563
1564        fn with_block(mut self, block: RecoveredBlock<Block>) -> Self {
1565            self.block = Some(block);
1566            self
1567        }
1568
1569        fn new_blocking(started: SyncSender<()>, release: Receiver<()>) -> Self {
1570            Self {
1571                bal_store: BalStoreHandle::new(TestBalStore {
1572                    fetches: Arc::new(AtomicUsize::default()),
1573                    blocking_fetch: Some(BlockingFetch { started, release: Mutex::new(release) }),
1574                }),
1575                block: None,
1576            }
1577        }
1578    }
1579
1580    impl BalProvider for TestBalProvider {
1581        fn bal_store(&self) -> &BalStoreHandle {
1582            &self.bal_store
1583        }
1584    }
1585
1586    #[derive(Debug)]
1587    struct TestBalStore {
1588        fetches: Arc<AtomicUsize>,
1589        blocking_fetch: Option<BlockingFetch>,
1590    }
1591
1592    #[derive(Debug)]
1593    struct BlockingFetch {
1594        started: SyncSender<()>,
1595        release: Mutex<Receiver<()>>,
1596    }
1597
1598    impl BalStore for TestBalStore {
1599        fn insert(&self, _num_hash: NumHash, _bal: reth_storage_api::RawBal) -> ProviderResult<()> {
1600            Ok(())
1601        }
1602
1603        fn prune(&self, _tip: BlockNumber) -> ProviderResult<usize> {
1604            Ok(0)
1605        }
1606
1607        fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
1608            self.fetches.fetch_add(1, Ordering::SeqCst);
1609            if let Some(blocking_fetch) = &self.blocking_fetch {
1610                blocking_fetch.started.send(()).unwrap();
1611                blocking_fetch.release.lock().unwrap().recv().unwrap();
1612            }
1613            Ok(block_hashes.iter().map(|_| Some(Bytes::from_static(&[0xc0]))).collect())
1614        }
1615    }
1616
1617    impl BlockHashReader for TestBalProvider {
1618        fn block_hash(&self, _number: BlockNumber) -> ProviderResult<Option<B256>> {
1619            Ok(None)
1620        }
1621
1622        fn canonical_hashes_range(
1623            &self,
1624            _start: BlockNumber,
1625            _end: BlockNumber,
1626        ) -> ProviderResult<Vec<B256>> {
1627            Ok(Vec::new())
1628        }
1629    }
1630
1631    impl BlockNumReader for TestBalProvider {
1632        fn chain_info(&self) -> ProviderResult<reth_chainspec::ChainInfo> {
1633            Ok(reth_chainspec::ChainInfo::default())
1634        }
1635
1636        fn best_block_number(&self) -> ProviderResult<BlockNumber> {
1637            Ok(0)
1638        }
1639
1640        fn last_block_number(&self) -> ProviderResult<BlockNumber> {
1641            Ok(0)
1642        }
1643
1644        fn block_number(&self, _hash: B256) -> ProviderResult<Option<BlockNumber>> {
1645            Ok(None)
1646        }
1647    }
1648
1649    impl HeaderProvider for TestBalProvider {
1650        type Header = Header;
1651
1652        fn header(&self, _block_hash: BlockHash) -> ProviderResult<Option<Self::Header>> {
1653            Ok(None)
1654        }
1655
1656        fn header_by_number(&self, _num: u64) -> ProviderResult<Option<Self::Header>> {
1657            Ok(None)
1658        }
1659
1660        fn headers_range(
1661            &self,
1662            _range: impl RangeBounds<BlockNumber>,
1663        ) -> ProviderResult<Vec<Self::Header>> {
1664            Ok(Vec::new())
1665        }
1666
1667        fn sealed_header(
1668            &self,
1669            _number: BlockNumber,
1670        ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
1671            Ok(None)
1672        }
1673
1674        fn sealed_headers_while(
1675            &self,
1676            _range: impl RangeBounds<BlockNumber>,
1677            _predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
1678        ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
1679            Ok(Vec::new())
1680        }
1681    }
1682
1683    impl BlockBodyIndicesProvider for TestBalProvider {
1684        fn block_body_indices(&self, _num: u64) -> ProviderResult<Option<StoredBlockBodyIndices>> {
1685            Ok(None)
1686        }
1687
1688        fn block_body_indices_range(
1689            &self,
1690            _range: RangeInclusive<BlockNumber>,
1691        ) -> ProviderResult<Vec<StoredBlockBodyIndices>> {
1692            Ok(Vec::new())
1693        }
1694    }
1695
1696    impl TransactionsProvider for TestBalProvider {
1697        type Transaction = TransactionSigned;
1698
1699        fn transaction_id(&self, _tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
1700            Ok(None)
1701        }
1702
1703        fn transaction_by_id(&self, _id: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
1704            Ok(None)
1705        }
1706
1707        fn transaction_by_id_unhashed(
1708            &self,
1709            _id: TxNumber,
1710        ) -> ProviderResult<Option<Self::Transaction>> {
1711            Ok(None)
1712        }
1713
1714        fn transaction_by_hash(&self, _hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
1715            Ok(None)
1716        }
1717
1718        fn transaction_by_hash_with_meta(
1719            &self,
1720            _hash: TxHash,
1721        ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
1722            Ok(None)
1723        }
1724
1725        fn transactions_by_block(
1726            &self,
1727            _block: BlockHashOrNumber,
1728        ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
1729            Ok(None)
1730        }
1731
1732        fn transactions_by_block_range(
1733            &self,
1734            _range: impl RangeBounds<BlockNumber>,
1735        ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
1736            Ok(Vec::new())
1737        }
1738
1739        fn transactions_by_tx_range(
1740            &self,
1741            _range: impl RangeBounds<TxNumber>,
1742        ) -> ProviderResult<Vec<Self::Transaction>> {
1743            Ok(Vec::new())
1744        }
1745
1746        fn senders_by_tx_range(
1747            &self,
1748            _range: impl RangeBounds<TxNumber>,
1749        ) -> ProviderResult<Vec<Address>> {
1750            Ok(Vec::new())
1751        }
1752
1753        fn transaction_sender(&self, _id: TxNumber) -> ProviderResult<Option<Address>> {
1754            Ok(None)
1755        }
1756    }
1757
1758    impl ReceiptProvider for TestBalProvider {
1759        type Receipt = Receipt;
1760
1761        fn receipt(&self, _id: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
1762            Ok(None)
1763        }
1764
1765        fn receipt_by_hash(&self, _hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
1766            Ok(None)
1767        }
1768
1769        fn receipts_by_block(
1770            &self,
1771            _block: BlockHashOrNumber,
1772        ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
1773            Ok(None)
1774        }
1775
1776        fn receipts_by_tx_range(
1777            &self,
1778            _range: impl RangeBounds<TxNumber>,
1779        ) -> ProviderResult<Vec<Self::Receipt>> {
1780            Ok(Vec::new())
1781        }
1782
1783        fn receipts_by_block_range(
1784            &self,
1785            _block_range: RangeInclusive<BlockNumber>,
1786        ) -> ProviderResult<Vec<Vec<Self::Receipt>>> {
1787            Ok(Vec::new())
1788        }
1789    }
1790
1791    impl BlockReader for TestBalProvider {
1792        type Block = Block;
1793
1794        fn find_block_by_hash(
1795            &self,
1796            _hash: B256,
1797            _source: BlockSource,
1798        ) -> ProviderResult<Option<Self::Block>> {
1799            Ok(None)
1800        }
1801
1802        fn block(&self, _id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> {
1803            Ok(None)
1804        }
1805
1806        fn pending_block(&self) -> ProviderResult<Option<Arc<RecoveredBlock<Self::Block>>>> {
1807            Ok(None)
1808        }
1809
1810        fn pending_block_and_receipts(
1811            &self,
1812        ) -> ProviderResult<Option<RecoveredBlockAndExecutionOutput<Self::Block, Self::Receipt>>>
1813        {
1814            Ok(None)
1815        }
1816
1817        fn recovered_block(
1818            &self,
1819            _id: BlockHashOrNumber,
1820            _transaction_kind: TransactionVariant,
1821        ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
1822            Ok(None)
1823        }
1824
1825        fn sealed_block_with_senders(
1826            &self,
1827            _id: BlockHashOrNumber,
1828            _transaction_kind: TransactionVariant,
1829        ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
1830            Ok(self.block.clone())
1831        }
1832
1833        fn block_range(
1834            &self,
1835            _range: RangeInclusive<BlockNumber>,
1836        ) -> ProviderResult<Vec<Self::Block>> {
1837            Ok(Vec::new())
1838        }
1839
1840        fn block_with_senders_range(
1841            &self,
1842            _range: RangeInclusive<BlockNumber>,
1843        ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
1844            Ok(Vec::new())
1845        }
1846
1847        fn recovered_block_range(
1848            &self,
1849            _range: RangeInclusive<BlockNumber>,
1850        ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
1851            Ok(Vec::new())
1852        }
1853
1854        fn block_by_transaction_id(&self, _id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
1855            Ok(None)
1856        }
1857    }
1858}