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