1use 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
48type BlockWithSendersResponseSender<B> =
50 oneshot::Sender<ProviderResult<Option<Arc<RecoveredBlock<B>>>>>;
51
52type 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
62type TransactionHashResponseSender<B, R> = oneshot::Sender<Option<CachedTransaction<B, R>>>;
64
65type 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#[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 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 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 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 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 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 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 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 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 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 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 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 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 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#[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#[must_use = "Type does nothing unless spawned"]
322pub(crate) struct EthStateCacheService<Provider>
323where
324 Provider: BlockReader + BalProvider,
325{
326 provider: Provider,
328 full_block_cache: BlockLruCache<Provider::Block>,
330 receipts_cache: ReceiptsLruCache<Provider::Receipt>,
332 bal_cache: BalLruCache,
334 idle_timeout: Duration,
336 eviction_interval: Interval,
338 eviction_pending: bool,
340 action_tx: UnboundedSender<CacheAction<Provider::Block, Provider::Receipt>>,
342 action_rx: UnboundedReceiverStream<CacheAction<Provider::Block, Provider::Receipt>>,
344 action_task_spawner: Runtime,
346 rate_limiter: Arc<Semaphore>,
350 pending_fetches: VecDeque<CacheFetch>,
355 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 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 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 for tx in queued {
452 let _ = tx.send(res.clone());
453 }
454 }
455
456 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 for tx in queued {
471 let _ = tx.send(res.clone());
472 }
473 }
474
475 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 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 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 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 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 let now = LazyCell::new(Instant::now);
562
563 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 cx.waker().wake_by_ref();
575 }
576 }
577
578 loop {
579 let Poll::Ready(action) = this.action_rx.poll_next_unpin(cx) else {
580 this.shrink_queues();
582 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 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 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 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 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 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
715enum 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 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
775struct 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 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#[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#[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
880pub 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#[derive(Clone, Debug)]
907pub(crate) struct CachedBal(Arc<DecodedEvmBal>);
908
909impl CachedBal {
910 #[inline]
912 fn new(bal: DecodedEvmBal) -> Self {
913 Self(Arc::new(bal))
914 }
915
916 #[inline]
919 const fn from_shared(bal: Arc<DecodedEvmBal>) -> Self {
920 Self(bal)
921 }
922
923 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 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 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}