ethers_middleware/gas_oracle/
cache.rs

1use super::{GasOracle, Result};
2use async_trait::async_trait;
3use ethers_core::types::U256;
4use futures_locks::RwLock;
5use instant::{Duration, Instant};
6use std::{fmt::Debug, future::Future};
7
8#[derive(Debug)]
9pub struct Cache<T: GasOracle> {
10    inner: T,
11    validity: Duration,
12    fee: Cached<U256>,
13    eip1559: Cached<(U256, U256)>,
14}
15
16#[derive(Default, Debug)]
17struct Cached<T: Clone>(RwLock<Option<(Instant, T)>>);
18
19#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
20#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
21impl<T: GasOracle> GasOracle for Cache<T> {
22    async fn fetch(&self) -> Result<U256> {
23        self.fee.get(self.validity, || self.inner.fetch()).await
24    }
25
26    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
27        self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
28    }
29}
30
31impl<T: GasOracle> Cache<T> {
32    pub fn new(validity: Duration, inner: T) -> Self {
33        Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
34    }
35}
36
37impl<T: Clone> Cached<T> {
38    async fn get<F, E, Fut>(&self, validity: Duration, fetch: F) -> Result<T, E>
39    where
40        F: FnOnce() -> Fut,
41        Fut: Future<Output = Result<T, E>>,
42    {
43        // Try with a read lock
44        {
45            let lock = self.0.read().await;
46            if let Some((last_fetch, value)) = lock.as_ref() {
47                if Instant::now().duration_since(*last_fetch) < validity {
48                    return Ok(value.clone())
49                }
50            }
51        }
52        // Acquire a write lock
53        {
54            let mut lock = self.0.write().await;
55            // Check again, a concurrent thread may have raced us to the write.
56            if let Some((last_fetch, value)) = lock.as_ref() {
57                if Instant::now().duration_since(*last_fetch) < validity {
58                    return Ok(value.clone())
59                }
60            }
61            // Set a fresh value
62            let value = fetch().await?;
63            *lock = Some((Instant::now(), value.clone()));
64            Ok(value)
65        }
66    }
67}