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