1use crate::{
2 utils, ParamsWithBlock, Provider, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock,
3};
4use alloy_eips::BlockId;
5use alloy_json_rpc::{RpcError, RpcSend};
6use alloy_network::Network;
7use alloy_network_primitives::TransactionResponse;
8use alloy_primitives::{
9 keccak256, Address, Bytes, StorageKey, StorageValue, TxHash, B256, U256, U64,
10};
11use alloy_rpc_types_eth::{
12 BlockNumberOrTag, EIP1186AccountProofResponse, Filter, Log, StorageValuesRequest,
13 StorageValuesResponse,
14};
15use alloy_transport::{TransportErrorKind, TransportResult};
16use lru::LruCache;
17use parking_lot::RwLock;
18use serde::{Deserialize, Serialize};
19use std::{io::BufReader, marker::PhantomData, num::NonZero, path::PathBuf, sync::Arc};
20#[derive(Debug, Clone)]
32pub struct CacheLayer {
33 cache: SharedCache,
35}
36
37impl CacheLayer {
38 pub fn new(max_items: u32) -> Self {
41 Self { cache: SharedCache::new(max_items) }
42 }
43
44 pub const fn max_items(&self) -> u32 {
46 self.cache.max_items()
47 }
48
49 pub fn cache(&self) -> SharedCache {
51 self.cache.clone()
52 }
53}
54
55impl<P, N> ProviderLayer<P, N> for CacheLayer
56where
57 P: Provider<N>,
58 N: Network,
59{
60 type Provider = CacheProvider<P, N>;
61
62 fn layer(&self, inner: P) -> Self::Provider {
63 CacheProvider::new(inner, self.cache())
64 }
65}
66
67#[derive(Debug, Clone)]
74pub struct CacheProvider<P, N> {
75 inner: P,
77 cache: SharedCache,
79 _pd: PhantomData<N>,
81}
82
83impl<P, N> CacheProvider<P, N>
84where
85 P: Provider<N>,
86 N: Network,
87{
88 pub const fn new(inner: P, cache: SharedCache) -> Self {
90 Self { inner, cache, _pd: PhantomData }
91 }
92}
93
94macro_rules! rpc_call_with_block {
102 ($cache:expr, $client:expr, $req:expr) => {{
103 let client =
104 $client.upgrade().ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"));
105 let cache = $cache.clone();
106 ProviderCall::BoxedFuture(Box::pin(async move {
107 let client = client?;
108
109 let result = client.request($req.method(), $req.params()).map_params(|params| {
110 ParamsWithBlock::new(params, $req.block_id.unwrap_or(BlockId::latest()))
111 });
112
113 let res = result.await?;
114 if $req.should_cache() {
117 let json_str = serde_json::to_string(&res).map_err(TransportErrorKind::custom)?;
118 let hash = $req.params_hash()?;
119 let _ = cache.put(hash, json_str);
120 }
121
122 Ok(res)
123 }))
124 }};
125}
126
127macro_rules! cache_rpc_call_with_block {
133 ($cache:expr, $client:expr, $req:expr) => {{
134 if !$req.should_cache() {
135 return rpc_call_with_block!($cache, $client, $req);
136 }
137
138 let hash = $req.params_hash().ok();
139
140 if let Some(hash) = hash {
141 if let Ok(Some(cached)) = $cache.get_deserialized(&hash) {
142 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
143 }
144 }
145
146 rpc_call_with_block!($cache, $client, $req)
147 }};
148}
149
150#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
151#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
152impl<P, N> Provider<N> for CacheProvider<P, N>
153where
154 P: Provider<N>,
155 N: Network,
156{
157 #[inline(always)]
158 fn root(&self) -> &RootProvider<N> {
159 self.inner.root()
160 }
161
162 fn get_block_receipts(
163 &self,
164 block: BlockId,
165 ) -> ProviderCall<(BlockId,), Option<Vec<N::ReceiptResponse>>> {
166 let req = RequestType::new("eth_getBlockReceipts", (block,)).with_block_id(block);
167
168 let should_cache = req.should_cache();
169
170 if should_cache {
171 let params_hash = req.params_hash().ok();
172
173 if let Some(hash) = params_hash {
174 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
175 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
176 }
177 }
178 }
179
180 let client = self.inner.weak_client();
181 let cache = self.cache.clone();
182
183 ProviderCall::BoxedFuture(Box::pin(async move {
184 let client = client
185 .upgrade()
186 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
187
188 let result = client.request(req.method(), req.params()).await?;
189
190 if should_cache {
191 if let Some(ref receipts) = result {
192 let json_str =
193 serde_json::to_string(receipts).map_err(TransportErrorKind::custom)?;
194 let hash = req.params_hash()?;
195 let _ = cache.put(hash, json_str);
196 }
197 }
198
199 Ok(result)
200 }))
201 }
202
203 fn get_balance(&self, address: Address) -> RpcWithBlock<Address, U256> {
204 let client = self.inner.weak_client();
205 let cache = self.cache.clone();
206 RpcWithBlock::new_provider(move |block_id| {
207 let req = RequestType::new("eth_getBalance", address).with_block_id(block_id);
208 cache_rpc_call_with_block!(cache, client, req)
209 })
210 }
211
212 fn get_code_at(&self, address: Address) -> RpcWithBlock<Address, Bytes> {
213 let client = self.inner.weak_client();
214 let cache = self.cache.clone();
215 RpcWithBlock::new_provider(move |block_id| {
216 let req = RequestType::new("eth_getCode", address).with_block_id(block_id);
217 cache_rpc_call_with_block!(cache, client, req)
218 })
219 }
220
221 async fn get_logs(&self, filter: &Filter) -> TransportResult<Vec<Log>> {
222 if filter.block_option.as_block_hash().is_none() {
223 let from_is_number = filter
225 .block_option
226 .get_from_block()
227 .as_ref()
228 .is_some_and(|block| block.is_number());
229 let to_is_number =
230 filter.block_option.get_to_block().as_ref().is_some_and(|block| block.is_number());
231
232 if !from_is_number || !to_is_number {
233 return self.inner.get_logs(filter).await;
234 }
235 }
236
237 let req = RequestType::new("eth_getLogs", (filter,));
238
239 let params_hash = req.params_hash().ok();
240
241 if let Some(hash) = params_hash {
242 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
243 return Ok(cached);
244 }
245 }
246
247 let result = self.inner.get_logs(filter).await?;
248
249 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
250
251 let hash = req.params_hash()?;
252 let _ = self.cache.put(hash, json_str);
253
254 Ok(result)
255 }
256
257 fn get_proof(
258 &self,
259 address: Address,
260 keys: Vec<StorageKey>,
261 ) -> RpcWithBlock<(Address, Vec<StorageKey>), EIP1186AccountProofResponse> {
262 let client = self.inner.weak_client();
263 let cache = self.cache.clone();
264 RpcWithBlock::new_provider(move |block_id| {
265 let req =
266 RequestType::new("eth_getProof", (address, keys.clone())).with_block_id(block_id);
267 cache_rpc_call_with_block!(cache, client, req)
268 })
269 }
270
271 fn get_storage_at(
272 &self,
273 address: Address,
274 key: U256,
275 ) -> RpcWithBlock<(Address, U256), StorageValue> {
276 let client = self.inner.weak_client();
277 let cache = self.cache.clone();
278 RpcWithBlock::new_provider(move |block_id| {
279 let req = RequestType::new("eth_getStorageAt", (address, key)).with_block_id(block_id);
280 cache_rpc_call_with_block!(cache, client, req)
281 })
282 }
283
284 fn get_storage_values(
285 &self,
286 requests: StorageValuesRequest,
287 ) -> RpcWithBlock<(StorageValuesRequest,), StorageValuesResponse> {
288 let client = self.inner.weak_client();
289 let cache = self.cache.clone();
290 RpcWithBlock::new_provider(move |block_id| {
291 let req = RequestType::new("eth_getStorageValues", (requests.clone(),))
292 .with_block_id(block_id);
293 cache_rpc_call_with_block!(cache, client, req)
294 })
295 }
296
297 fn get_transaction_by_hash(
298 &self,
299 hash: TxHash,
300 ) -> ProviderCall<(TxHash,), Option<N::TransactionResponse>> {
301 let req = RequestType::new("eth_getTransactionByHash", (hash,));
302
303 let params_hash = req.params_hash().ok();
304
305 if let Some(hash) = params_hash {
306 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
307 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
308 }
309 }
310 let client = self.inner.weak_client();
311 let cache = self.cache.clone();
312 ProviderCall::BoxedFuture(Box::pin(async move {
313 let client = client
314 .upgrade()
315 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
316 let result: Option<N::TransactionResponse> =
317 client.request(req.method(), req.params()).await?;
318
319 if let Some(ref tx) = result {
320 if tx.block_hash_num().is_some() {
323 let json_str = serde_json::to_string(tx).map_err(TransportErrorKind::custom)?;
324 let hash = req.params_hash()?;
325 let _ = cache.put(hash, json_str);
326 }
327 }
328
329 Ok(result)
330 }))
331 }
332
333 fn get_raw_transaction_by_hash(&self, hash: TxHash) -> ProviderCall<(TxHash,), Option<Bytes>> {
334 let req = RequestType::new("eth_getRawTransactionByHash", (hash,));
335
336 let params_hash = req.params_hash().ok();
337
338 if let Some(hash) = params_hash {
339 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
340 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
341 }
342 }
343
344 let client = self.inner.weak_client();
345 let cache = self.cache.clone();
346 ProviderCall::BoxedFuture(Box::pin(async move {
347 let client = client
348 .upgrade()
349 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
350
351 let result = client.request(req.method(), req.params()).await?;
352
353 if let Some(ref tx) = result {
354 let json_str = serde_json::to_string(tx).map_err(TransportErrorKind::custom)?;
355 let hash = req.params_hash()?;
356 let _ = cache.put(hash, json_str);
357 }
358
359 Ok(result)
360 }))
361 }
362
363 fn get_transaction_receipt(
364 &self,
365 hash: TxHash,
366 ) -> ProviderCall<(TxHash,), Option<N::ReceiptResponse>> {
367 let req = RequestType::new("eth_getTransactionReceipt", (hash,));
368
369 let params_hash = req.params_hash().ok();
370
371 if let Some(hash) = params_hash {
372 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
373 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
374 }
375 }
376
377 let client = self.inner.weak_client();
378 let cache = self.cache.clone();
379 ProviderCall::BoxedFuture(Box::pin(async move {
380 let client = client
381 .upgrade()
382 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
383
384 let result = client.request(req.method(), req.params()).await?;
385
386 if let Some(ref receipt) = result {
387 let json_str =
388 serde_json::to_string(receipt).map_err(TransportErrorKind::custom)?;
389 let hash = req.params_hash()?;
390 let _ = cache.put(hash, json_str);
391 }
392
393 Ok(result)
394 }))
395 }
396
397 fn get_transaction_count(
398 &self,
399 address: Address,
400 ) -> RpcWithBlock<Address, U64, u64, fn(U64) -> u64> {
401 let client = self.inner.weak_client();
402 let cache = self.cache.clone();
403 RpcWithBlock::new_provider(move |block_id| {
404 let req = RequestType::new("eth_getTransactionCount", address).with_block_id(block_id);
405
406 let should_cache = req.should_cache();
407
408 if should_cache {
409 let params_hash = req.params_hash().ok();
410
411 if let Some(hash) = params_hash {
412 if let Ok(Some(cached)) = cache.get_deserialized::<U64>(&hash) {
413 return ProviderCall::BoxedFuture(Box::pin(async move {
414 Ok(utils::convert_u64(cached))
415 }));
416 }
417 }
418 }
419
420 let client = client.clone();
421 let cache = cache.clone();
422
423 ProviderCall::BoxedFuture(Box::pin(async move {
424 let client = client
425 .upgrade()
426 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
427
428 let result: U64 = client
429 .request(req.method(), req.params())
430 .map_params(|params| ParamsWithBlock::new(params, block_id))
431 .await?;
432
433 if should_cache {
434 let json_str =
435 serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
436 let hash = req.params_hash()?;
437 let _ = cache.put(hash, json_str);
438 }
439
440 Ok(utils::convert_u64(result))
441 }))
442 })
443 }
444}
445
446struct RequestType<Params: RpcSend> {
448 method: &'static str,
449 params: Params,
450 block_id: Option<BlockId>,
451}
452
453impl<Params: RpcSend> RequestType<Params> {
454 const fn new(method: &'static str, params: Params) -> Self {
455 Self { method, params, block_id: None }
456 }
457
458 const fn with_block_id(mut self, block_id: BlockId) -> Self {
459 self.block_id = Some(block_id);
460 self
461 }
462
463 fn params_hash(&self) -> TransportResult<B256> {
464 let hash = serde_json::to_string(&self.params())
468 .map(|p| {
469 keccak256(
470 match self.block_id {
471 Some(BlockId::Hash(rpc_block_hash)) => {
472 format!("{}{}{}", rpc_block_hash, self.method(), p)
473 }
474 Some(BlockId::Number(BlockNumberOrTag::Number(number))) => {
475 format!("{}{}{}", number, self.method(), p)
476 }
477 _ => format!("{}{}", self.method(), p),
478 }
479 .as_bytes(),
480 )
481 })
482 .map_err(RpcError::ser_err)?;
483
484 Ok(hash)
485 }
486
487 const fn method(&self) -> &'static str {
488 self.method
489 }
490
491 fn params(&self) -> Params {
492 self.params.clone()
493 }
494
495 const fn should_cache(&self) -> bool {
497 if let Some(block_id) = self.block_id {
498 return match block_id {
499 BlockId::Hash(hash) => !matches!(hash.require_canonical, Some(true)),
500 BlockId::Number(BlockNumberOrTag::Number(_)) => true,
501 _ => false,
502 };
503 }
504
505 false
508 }
509}
510
511#[derive(Debug, Serialize, Deserialize)]
512struct FsCacheEntry {
513 key: B256,
515 value: String,
517}
518
519#[derive(Debug, Clone)]
521pub struct SharedCache {
522 inner: Arc<RwLock<LruCache<B256, String, alloy_primitives::map::FbBuildHasher<32>>>>,
523 max_items: NonZero<usize>,
524}
525
526impl SharedCache {
527 pub fn new(max_items: u32) -> Self {
529 let max_items = NonZero::new(max_items as usize).unwrap_or(NonZero::<usize>::MIN);
530 let inner = Arc::new(RwLock::new(LruCache::with_hasher(max_items, Default::default())));
531 Self { inner, max_items }
532 }
533
534 pub const fn max_items(&self) -> u32 {
536 self.max_items.get() as u32
537 }
538
539 pub fn put(&self, key: B256, value: String) -> TransportResult<bool> {
541 Ok(self.inner.write().put(key, value).is_some())
542 }
543
544 pub fn get(&self, key: &B256) -> Option<String> {
546 self.inner.write().get(key).cloned()
548 }
549
550 pub fn get_deserialized<T>(&self, key: &B256) -> TransportResult<Option<T>>
552 where
553 T: for<'de> Deserialize<'de>,
554 {
555 let Some(cached) = self.get(key) else { return Ok(None) };
556 let result = serde_json::from_str(&cached).map_err(TransportErrorKind::custom)?;
557 Ok(Some(result))
558 }
559
560 pub fn save_cache(&self, path: PathBuf) -> TransportResult<()> {
564 let entries: Vec<FsCacheEntry> = {
565 self.inner
566 .read()
567 .iter()
568 .map(|(key, value)| FsCacheEntry { key: *key, value: value.clone() })
569 .collect()
570 };
571 let file = std::fs::File::create(path).map_err(TransportErrorKind::custom)?;
572 serde_json::to_writer(file, &entries).map_err(TransportErrorKind::custom)?;
573 Ok(())
574 }
575
576 pub fn load_cache(&self, path: PathBuf) -> TransportResult<()> {
581 if !path.exists() {
582 return Ok(());
583 };
584 let file = std::fs::File::open(path).map_err(TransportErrorKind::custom)?;
585 let file = BufReader::new(file);
586 let entries: Vec<FsCacheEntry> =
587 serde_json::from_reader(file).map_err(TransportErrorKind::custom)?;
588 let mut cache = self.inner.write();
589 for entry in entries {
590 cache.put(entry.key, entry.value);
591 }
592
593 Ok(())
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use crate::ProviderBuilder;
601 use alloy_network::TransactionBuilder;
602 use alloy_node_bindings::{utils::run_with_tempdir, Anvil};
603 use alloy_primitives::{b256, bytes, hex, utils::Unit, Bytes, FixedBytes};
604 use alloy_rpc_types_eth::{BlockId, Transaction, TransactionReceipt, TransactionRequest};
605 use alloy_transport::mock::Asserter;
606
607 #[tokio::test]
608 async fn test_get_proof() {
609 run_with_tempdir("get-proof", |dir| async move {
610 let cache_layer = CacheLayer::new(100);
611 let shared_cache = cache_layer.cache();
612 let anvil = Anvil::new().block_time_f64(0.3).spawn();
613 let provider = ProviderBuilder::new().layer(cache_layer).connect_http(anvil.endpoint_url());
614
615 let from = anvil.addresses()[0];
616 let path = dir.join("rpc-cache-proof.txt");
617
618 shared_cache.load_cache(path.clone()).unwrap();
619
620 let calldata: Bytes = "0x6080604052348015600f57600080fd5b506101f28061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633fb5c1cb146100465780638381f58a14610062578063d09de08a14610080575b600080fd5b610060600480360381019061005b91906100ee565b61008a565b005b61006a610094565b604051610077919061012a565b60405180910390f35b61008861009a565b005b8060008190555050565b60005481565b6000808154809291906100ac90610174565b9190505550565b600080fd5b6000819050919050565b6100cb816100b8565b81146100d657600080fd5b50565b6000813590506100e8816100c2565b92915050565b600060208284031215610104576101036100b3565b5b6000610112848285016100d9565b91505092915050565b610124816100b8565b82525050565b600060208201905061013f600083018461011b565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061017f826100b8565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101b1576101b0610145565b5b60018201905091905056fea264697066735822122067ac0f21f648b0cacd1b7260772852ad4a0f63e2cc174168c51a6887fd5197a964736f6c634300081a0033".parse().unwrap();
621
622 let tx = TransactionRequest::default()
623 .with_from(from)
624 .with_input(calldata)
625 .with_max_fee_per_gas(1_000_000_000)
626 .with_max_priority_fee_per_gas(1_000_000)
627 .with_gas_limit(1_000_000)
628 .with_nonce(0);
629
630 let tx_receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
631
632 let counter_addr = tx_receipt.contract_address.unwrap();
633
634 let keys = vec![
635 FixedBytes::with_last_byte(0),
636 FixedBytes::with_last_byte(0x1),
637 FixedBytes::with_last_byte(0x2),
638 FixedBytes::with_last_byte(0x3),
639 FixedBytes::with_last_byte(0x4),
640 ];
641
642 let proof =
643 provider.get_proof(counter_addr, keys.clone()).block_id(1.into()).await.unwrap();
644 let proof2 = provider.get_proof(counter_addr, keys).block_id(1.into()).await.unwrap();
645
646 assert_eq!(proof, proof2);
647
648 shared_cache.save_cache(path).unwrap();
649 }).await;
650 }
651
652 #[tokio::test]
653 async fn test_get_tx_by_hash_and_receipt() {
654 run_with_tempdir("get-tx-by-hash", |dir| async move {
655 let cache_layer = CacheLayer::new(100);
656 let shared_cache = cache_layer.cache();
657 let anvil = Anvil::new().block_time_f64(0.3).spawn();
658 let provider = ProviderBuilder::new()
659 .disable_recommended_fillers()
660 .layer(cache_layer)
661 .connect_http(anvil.endpoint_url());
662
663 let path = dir.join("rpc-cache-tx.txt");
664 shared_cache.load_cache(path.clone()).unwrap();
665
666 let req = TransactionRequest::default()
667 .from(anvil.addresses()[0])
668 .to(Address::repeat_byte(5))
669 .value(U256::ZERO)
670 .input(bytes!("deadbeef").into());
671
672 let tx_hash =
673 *provider.send_transaction(req).await.expect("failed to send tx").tx_hash();
674
675 let tx = provider.get_transaction_by_hash(tx_hash).await.unwrap(); let tx2 = provider.get_transaction_by_hash(tx_hash).await.unwrap(); assert_eq!(tx, tx2);
678
679 let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap(); let receipt2 = provider.get_transaction_receipt(tx_hash).await.unwrap(); assert_eq!(receipt, receipt2);
683
684 shared_cache.save_cache(path).unwrap();
685 })
686 .await;
687 }
688
689 #[tokio::test]
690 async fn test_get_transaction_by_hash_retries_after_none() {
691 let cache_layer = CacheLayer::new(100);
692 let shared_cache = cache_layer.cache();
693 let asserter = Asserter::new();
694 let provider = ProviderBuilder::new()
695 .disable_recommended_fillers()
696 .layer(cache_layer)
697 .connect_mocked_client(asserter.clone());
698
699 let tx_hash = b256!("018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f");
700 let req = RequestType::new("eth_getTransactionByHash", (tx_hash,));
701 let cache_key = req.params_hash().unwrap();
702
703 let tx: Transaction = serde_json::from_str(
704 r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x6e4e53d1de650d5a5ebed19b38321db369ef1dc357904284ecf4d89b8834969c","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#,
705 )
706 .unwrap();
707
708 asserter.push_success(&Option::<Transaction>::None);
709 asserter.push_success(&Some(tx.clone()));
710
711 let first = provider.get_transaction_by_hash(tx_hash).await.unwrap();
712 assert_eq!(first, None);
713 assert!(shared_cache.get(&cache_key).is_none());
714
715 let second = provider.get_transaction_by_hash(tx_hash).await.unwrap();
716 assert_eq!(second, Some(tx));
717 assert!(shared_cache.get(&cache_key).is_some());
718 }
719
720 #[tokio::test]
721 async fn test_hash_canonical_block_ids_do_not_use_cache() {
722 let cache_layer = CacheLayer::new(100);
723 let shared_cache = cache_layer.cache();
724 let asserter = Asserter::new();
725 let provider = ProviderBuilder::new()
726 .disable_recommended_fillers()
727 .layer(cache_layer)
728 .connect_mocked_client(asserter.clone());
729
730 let address = Address::repeat_byte(5);
731 let block_hash = B256::repeat_byte(0x11);
732 let block_id = BlockId::hash_canonical(block_hash);
733 let req = RequestType::new("eth_getBalance", address).with_block_id(block_id);
734 let cache_key = req.params_hash().unwrap();
735
736 asserter.push_success(&U256::from(456));
737 asserter.push_failure_msg("block is not canonical");
738
739 let first = provider.get_balance(address).block_id(block_id).await.unwrap();
740 assert_eq!(first, U256::from(456));
741 assert!(shared_cache.get(&cache_key).is_none());
742
743 let second = provider.get_balance(address).block_id(block_id).await;
744 assert!(second.is_err());
745 }
746
747 #[tokio::test]
748 async fn test_get_transaction_by_hash_does_not_cache_pending() {
749 let cache_layer = CacheLayer::new(100);
750 let shared_cache = cache_layer.cache();
751 let asserter = Asserter::new();
752 let provider = ProviderBuilder::new()
753 .disable_recommended_fillers()
754 .layer(cache_layer)
755 .connect_mocked_client(asserter.clone());
756
757 let tx_hash = b256!("018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f");
758 let req = RequestType::new("eth_getTransactionByHash", (tx_hash,));
759 let cache_key = req.params_hash().unwrap();
760
761 let pending_tx: Transaction = serde_json::from_str(
762 r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#,
763 )
764 .unwrap();
765
766 let mined_tx: Transaction = serde_json::from_str(
767 r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x6e4e53d1de650d5a5ebed19b38321db369ef1dc357904284ecf4d89b8834969c","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#,
768 )
769 .unwrap();
770
771 asserter.push_success(&Some(pending_tx.clone()));
772 asserter.push_success(&Some(mined_tx.clone()));
773
774 let first = provider.get_transaction_by_hash(tx_hash).await.unwrap();
775 assert_eq!(first, Some(pending_tx));
776 assert!(shared_cache.get(&cache_key).is_none());
777
778 let second = provider.get_transaction_by_hash(tx_hash).await.unwrap();
779 assert_eq!(second, Some(mined_tx.clone()));
780 assert!(shared_cache.get(&cache_key).is_some());
781
782 let third = provider.get_transaction_by_hash(tx_hash).await.unwrap();
784 assert_eq!(third, Some(mined_tx));
785 }
786
787 #[tokio::test]
788 async fn test_get_raw_transaction_by_hash_retries_after_none() {
789 let cache_layer = CacheLayer::new(100);
790 let shared_cache = cache_layer.cache();
791 let asserter = Asserter::new();
792 let provider = ProviderBuilder::new()
793 .disable_recommended_fillers()
794 .layer(cache_layer)
795 .connect_mocked_client(asserter.clone());
796
797 let tx_hash = TxHash::with_last_byte(1);
798 let req = RequestType::new("eth_getRawTransactionByHash", (tx_hash,));
799 let cache_key = req.params_hash().unwrap();
800 let raw_tx = bytes!("deadbeef");
801
802 asserter.push_success(&Option::<Bytes>::None);
803 asserter.push_success(&Some(raw_tx.clone()));
804
805 let first = provider.get_raw_transaction_by_hash(tx_hash).await.unwrap();
806 assert_eq!(first, None);
807 assert!(shared_cache.get(&cache_key).is_none());
808
809 let second = provider.get_raw_transaction_by_hash(tx_hash).await.unwrap();
810 assert_eq!(second, Some(raw_tx));
811 assert!(shared_cache.get(&cache_key).is_some());
812 }
813
814 #[tokio::test]
815 async fn test_get_transaction_receipt_retries_after_none() {
816 let cache_layer = CacheLayer::new(100);
817 let shared_cache = cache_layer.cache();
818 let asserter = Asserter::new();
819 let provider = ProviderBuilder::new()
820 .disable_recommended_fillers()
821 .layer(cache_layer)
822 .connect_mocked_client(asserter.clone());
823
824 let tx_hash = b256!("ea1093d492a1dcb1bef708f771a99a96ff05dcab81ca76c31940300177fcf49f");
825 let req = RequestType::new("eth_getTransactionReceipt", (tx_hash,));
826 let cache_key = req.params_hash().unwrap();
827
828 let receipt: TransactionReceipt = serde_json::from_str(
829 r#"{
830 "transactionHash": "0xea1093d492a1dcb1bef708f771a99a96ff05dcab81ca76c31940300177fcf49f",
831 "blockHash": "0x8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e",
832 "blockNumber": "0xf4240",
833 "logsBloom": "0x00000000000000000000000000000000000800000000000000000000000800000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000",
834 "gasUsed": "0x723c",
835 "root": "0x284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10",
836 "contractAddress": null,
837 "cumulativeGasUsed": "0x723c",
838 "transactionIndex": "0x0",
839 "from": "0x39fa8c5f2793459d6622857e7d9fbb4bd91766d3",
840 "to": "0xc083e9947cf02b8ffc7d3090ae9aea72df98fd47",
841 "type": "0x0",
842 "effectiveGasPrice": "0x12bfb19e60",
843 "logs": [
844 {
845 "blockHash": "0x8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e",
846 "address": "0xc083e9947cf02b8ffc7d3090ae9aea72df98fd47",
847 "logIndex": "0x0",
848 "data": "0x00000000000000000000000039fa8c5f2793459d6622857e7d9fbb4bd91766d30000000000000000000000000000000000000000000000056bc75e2d63100000",
849 "removed": false,
850 "topics": [
851 "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"
852 ],
853 "blockNumber": "0xf4240",
854 "transactionIndex": "0x0",
855 "transactionHash": "0xea1093d492a1dcb1bef708f771a99a96ff05dcab81ca76c31940300177fcf49f"
856 }
857 ]
858 }"#,
859 )
860 .unwrap();
861
862 asserter.push_success(&Option::<TransactionReceipt>::None);
863 asserter.push_success(&Some(receipt.clone()));
864
865 let first = provider.get_transaction_receipt(tx_hash).await.unwrap();
866 assert_eq!(first, None);
867 assert!(shared_cache.get(&cache_key).is_none());
868
869 let second = provider.get_transaction_receipt(tx_hash).await.unwrap();
870 assert_eq!(second, Some(receipt));
871 assert!(shared_cache.get(&cache_key).is_some());
872 }
873
874 #[tokio::test]
875 async fn test_block_receipts() {
876 run_with_tempdir("get-block-receipts", |dir| async move {
877 let cache_layer = CacheLayer::new(100);
878 let shared_cache = cache_layer.cache();
879 let anvil = Anvil::new().spawn();
880 let provider = ProviderBuilder::new().layer(cache_layer).connect_http(anvil.endpoint_url());
881
882 let path = dir.join("rpc-cache-block-receipts.txt");
883 shared_cache.load_cache(path.clone()).unwrap();
884
885 let receipt = provider
888 .send_raw_transaction(
889 bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b").as_ref()
891 )
892 .await.unwrap().get_receipt().await.unwrap();
893
894 let block_number = receipt.block_number.unwrap();
895
896 let receipts =
897 provider.get_block_receipts(block_number.into()).await.unwrap(); let receipts2 =
899 provider.get_block_receipts(block_number.into()).await.unwrap(); assert_eq!(receipts, receipts2);
901
902 assert!(receipts.is_some_and(|r| r[0] == receipt));
903
904 shared_cache.save_cache(path).unwrap();
905 })
906 .await
907 }
908
909 #[tokio::test]
910 async fn test_get_balance() {
911 run_with_tempdir("get-balance", |dir| async move {
912 let cache_layer = CacheLayer::new(100);
913 let cache_layer2 = cache_layer.clone();
914 let shared_cache = cache_layer.cache();
915 let anvil = Anvil::new().spawn();
916 let provider = ProviderBuilder::new()
917 .disable_recommended_fillers()
918 .layer(cache_layer)
919 .connect_http(anvil.endpoint_url());
920
921 let path = dir.join("rpc-cache-balance.txt");
922 shared_cache.load_cache(path.clone()).unwrap();
923
924 let to = Address::repeat_byte(5);
925
926 let req = TransactionRequest::default()
928 .from(anvil.addresses()[0])
929 .to(to)
930 .value(Unit::ETHER.wei());
931
932 let receipt = provider
933 .send_transaction(req)
934 .await
935 .expect("failed to send tx")
936 .get_receipt()
937 .await
938 .unwrap();
939 let block_number = receipt.block_number.unwrap();
940
941 let balance = provider.get_balance(to).block_id(block_number.into()).await.unwrap();
943 assert_eq!(balance, Unit::ETHER.wei());
944
945 drop(anvil);
947
948 let provider2 = ProviderBuilder::new()
950 .disable_recommended_fillers()
951 .layer(cache_layer2)
952 .connect_http("http://localhost:1".parse().unwrap());
953
954 let balance2 = provider2.get_balance(to).block_id(block_number.into()).await.unwrap();
956 assert_eq!(balance, balance2);
957
958 shared_cache.save_cache(path).unwrap();
959 })
960 .await;
961 }
962
963 #[tokio::test]
964 async fn test_get_code() {
965 run_with_tempdir("get-code", |dir| async move {
966 let cache_layer = CacheLayer::new(100);
967 let shared_cache = cache_layer.cache();
968 let provider = ProviderBuilder::new().disable_recommended_fillers().with_gas_estimation().layer(cache_layer).connect_anvil_with_wallet();
969
970 let path = dir.join("rpc-cache-code.txt");
971 shared_cache.load_cache(path.clone()).unwrap();
972
973 let bytecode = hex::decode(
974 "6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
976 ).unwrap();
977 let tx = TransactionRequest::default().with_nonce(0).with_deploy_code(bytecode).with_chain_id(31337);
978
979 let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
980
981 let counter_addr = receipt.contract_address.unwrap();
982
983 let block_id = BlockId::number(receipt.block_number.unwrap());
984
985 let code = provider.get_code_at(counter_addr).block_id(block_id).await.unwrap(); let code2 = provider.get_code_at(counter_addr).block_id(block_id).await.unwrap(); assert_eq!(code, code2);
988
989 shared_cache.save_cache(path).unwrap();
990 })
991 .await;
992 }
993
994 #[cfg(all(test, feature = "anvil-api"))]
995 #[tokio::test]
996 async fn test_get_storage_at_different_block_ids() {
997 use crate::ext::AnvilApi;
998
999 run_with_tempdir("get-code-different-block-id", |dir| async move {
1000 let cache_layer = CacheLayer::new(100);
1001 let shared_cache = cache_layer.cache();
1002 let provider = ProviderBuilder::new().disable_recommended_fillers().with_gas_estimation().layer(cache_layer).connect_anvil_with_wallet();
1003
1004 let path = dir.join("rpc-cache-code.txt");
1005 shared_cache.load_cache(path.clone()).unwrap();
1006
1007 let bytecode = hex::decode(
1008 "6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
1010 ).unwrap();
1011
1012 let tx = TransactionRequest::default().with_nonce(0).with_deploy_code(bytecode).with_chain_id(31337);
1013 let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
1014 let counter_addr = receipt.contract_address.unwrap();
1015 let block_id = BlockId::number(receipt.block_number.unwrap());
1016
1017 let counter = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id).await.unwrap(); assert_eq!(counter, U256::ZERO);
1019 let counter_cached = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id).await.unwrap(); assert_eq!(counter, counter_cached);
1021
1022 provider.anvil_mine(Some(1), None).await.unwrap();
1023
1024 let tx2 = TransactionRequest::default().with_nonce(1).to(counter_addr).input(hex::decode("d09de08a").unwrap().into()).with_chain_id(31337);
1026 let receipt2 = provider.send_transaction(tx2).await.unwrap().get_receipt().await.unwrap();
1027 let block_id2 = BlockId::number(receipt2.block_number.unwrap());
1028
1029 let counter2 = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id2).await.unwrap(); assert_eq!(counter2, U256::from(1));
1031 let counter2_cached = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id2).await.unwrap(); assert_eq!(counter2, counter2_cached);
1033
1034 shared_cache.save_cache(path).unwrap();
1035 })
1036 .await;
1037 }
1038
1039 #[tokio::test]
1040 async fn test_get_transaction_count() {
1041 run_with_tempdir("get-tx-count", |dir| async move {
1042 let cache_layer = CacheLayer::new(100);
1043 let cache_layer2 = cache_layer.clone();
1045 let shared_cache = cache_layer.cache();
1046 let anvil = Anvil::new().spawn();
1047 let provider = ProviderBuilder::new()
1048 .disable_recommended_fillers()
1049 .layer(cache_layer)
1050 .connect_http(anvil.endpoint_url());
1051
1052 let path = dir.join("rpc-cache-tx-count.txt");
1053 shared_cache.load_cache(path.clone()).unwrap();
1054
1055 let address = anvil.addresses()[0];
1056
1057 let req = TransactionRequest::default()
1059 .from(address)
1060 .to(Address::repeat_byte(5))
1061 .value(U256::ZERO)
1062 .input(bytes!("deadbeef").into());
1063
1064 let receipt = provider
1065 .send_transaction(req)
1066 .await
1067 .expect("failed to send tx")
1068 .get_receipt()
1069 .await
1070 .unwrap();
1071 let block_number = receipt.block_number.unwrap();
1072
1073 let count = provider
1075 .get_transaction_count(address)
1076 .block_id(block_number.into())
1077 .await
1078 .unwrap();
1079 assert_eq!(count, 1);
1080
1081 drop(anvil);
1083
1084 let provider2 = ProviderBuilder::new()
1086 .disable_recommended_fillers()
1087 .layer(cache_layer2)
1088 .connect_http("http://localhost:1".parse().unwrap());
1089
1090 let count2 = provider2
1092 .get_transaction_count(address)
1093 .block_id(block_number.into())
1094 .await
1095 .unwrap();
1096 assert_eq!(count, count2);
1097
1098 shared_cache.save_cache(path).unwrap();
1099 })
1100 .await;
1101 }
1102}