Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(provider): LRUCache Layer #954

Merged
merged 57 commits into from
Oct 8, 2024
Merged
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
0d56877
in-memory cache implementation
yash-atreya Jun 21, 2024
df7d861
load and dump cache from fs
yash-atreya Jun 24, 2024
cc8b11d
use RwLock
yash-atreya Jun 24, 2024
8a1e353
doc nits
yash-atreya Jun 24, 2024
03c50c5
feat: CacheConfig, load/save at specific paths
yash-atreya Jun 24, 2024
0fc93a1
RequestType enum
yash-atreya Jun 24, 2024
9b95833
params hash
yash-atreya Jun 24, 2024
f79a128
clone and arc `CacheProvider`
yash-atreya Jun 24, 2024
e6da7f7
add: get_block_by_hash
yash-atreya Jun 24, 2024
902a24b
todos
yash-atreya Jun 24, 2024
863c868
Merge branch 'main' into yash/provider-cache
yash-atreya Jul 29, 2024
bb05544
refactor: port to transport layer
yash-atreya Aug 1, 2024
5fa27f4
rm provider cache layer
yash-atreya Aug 1, 2024
12a76c3
use parking_lot::RwLock + tracing nits
yash-atreya Aug 6, 2024
64343b0
cleanup nits
yash-atreya Aug 6, 2024
9fee441
nit
yash-atreya Aug 6, 2024
cb1c1b5
move cache instance to layer
yash-atreya Aug 7, 2024
6e9c7f4
Merge branch 'main' into yash/provider-cache
yash-atreya Aug 21, 2024
47ee0c6
resolve conflicts
yash-atreya Sep 19, 2024
5eb4af2
Revert "refactor: port to transport layer"
yash-atreya Sep 20, 2024
3e9538e
use provider cache
yash-atreya Sep 20, 2024
efa027a
use macro
yash-atreya Sep 20, 2024
ab44b80
cached get_proof
yash-atreya Sep 20, 2024
ae9e123
nit
yash-atreya Sep 20, 2024
9f646b2
nit
yash-atreya Sep 20, 2024
c25371a
Merge branch 'main' into yash/provider-cache
yash-atreya Sep 24, 2024
947bd91
use parking_lot
yash-atreya Sep 24, 2024
5653119
Merge branch 'main' into yash/provider-cache
yash-atreya Sep 27, 2024
c82901c
make params hash independent of client
yash-atreya Sep 27, 2024
32a7beb
fix
yash-atreya Sep 27, 2024
0478ded
cache_rpc_call_with_block!
yash-atreya Sep 27, 2024
fa8346c
fix: request type
yash-atreya Sep 27, 2024
6e0c676
redirect reqs with block tags to rpc
yash-atreya Sep 27, 2024
70bc5e1
nits
yash-atreya Sep 27, 2024
f8d1ab6
get_accounts
yash-atreya Sep 27, 2024
d8d4094
chain_id
yash-atreya Sep 27, 2024
d67142c
cfg gate wasm
yash-atreya Sep 30, 2024
8d0e89a
Merge branch 'main' into yash/provider-cache
yash-atreya Oct 4, 2024
2545bb0
rm get_accounts and get_chain_id
yash-atreya Oct 4, 2024
3214f63
rm related tests
yash-atreya Oct 4, 2024
dcfc38a
tests: run_with_temp_dir
yash-atreya Oct 4, 2024
0c88fe9
feat: SharedCache
yash-atreya Oct 4, 2024
d1c5e09
make CacheProvider generic over Network
yash-atreya Oct 4, 2024
cc0c1eb
add more methods
yash-atreya Oct 4, 2024
ce08a88
fmt
yash-atreya Oct 4, 2024
ccb67c1
docs
yash-atreya Oct 4, 2024
55fe2ff
docs
yash-atreya Oct 4, 2024
93d139d
nit
yash-atreya Oct 4, 2024
dcc4481
fix
yash-atreya Oct 4, 2024
60bd432
clippy
yash-atreya Oct 4, 2024
b77279f
mv SharedCache
yash-atreya Oct 5, 2024
8facd6f
use schnellru
yash-atreya Oct 5, 2024
62cfbeb
feat: get_derialized
yash-atreya Oct 5, 2024
6716c93
nits
yash-atreya Oct 5, 2024
19fd125
fix save_cache
yash-atreya Oct 8, 2024
6ec0026
nit
yash-atreya Oct 8, 2024
7e47495
nit
yash-atreya Oct 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 74 additions & 70 deletions crates/provider/src/layers/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,29 @@ use std::{io::BufReader, marker::PhantomData, num::NonZeroUsize, path::PathBuf,
/// file system by calling `save_cache`.
#[derive(Debug, Clone)]
pub struct CacheLayer {
/// Configuration for the cache layer.
config: CacheConfig,
yash-atreya marked this conversation as resolved.
Show resolved Hide resolved
/// In-memory LRU cache, mapping requests to responses.
cache: SharedCache,
}

impl CacheLayer {
/// Instantiate a new cache layer with the the maximum number of
/// items to store.
#[inline]
pub const fn new(max_items: usize) -> Self {
Self { config: CacheConfig { max_items } }
pub fn new(max_items: usize) -> Self {
Self { config: CacheConfig { max_items }, cache: SharedCache::new(max_items) }
}

/// Returns the maximum number of items that can be stored in the cache, set at initialization.
#[inline]
pub const fn max_items(&self) -> usize {
self.config.max_items
}

/// Returns the shared cache.
pub fn cache(&self) -> SharedCache {
self.cache.clone()
}
}

impl<P, T, N> ProviderLayer<P, T, N> for CacheLayer
Expand All @@ -50,7 +57,7 @@ where
type Provider = CacheProvider<P, T, N>;

fn layer(&self, inner: P) -> Self::Provider {
CacheProvider::new(inner, self.max_items())
CacheProvider::new(inner, self.cache())
}
}

Expand All @@ -77,44 +84,9 @@ where
N: Network,
{
/// Instantiate a new cache provider.
pub fn new(inner: P, max_items: usize) -> Self {
let cache = SharedCache::new(max_items);
pub fn new(inner: P, cache: SharedCache) -> Self {
Self { inner, cache, _pd: PhantomData }
}

/// Saves the cache to a file specified by the path.
/// If the files does not exist, it creates one.
/// If the file exists, it overwrites it.
pub fn save_cache(&self, path: PathBuf) -> TransportResult<()> {
let cache = self.cache.read();
let file = std::fs::File::create(path).map_err(TransportErrorKind::custom)?;

// Iterate over the cache and dump to the file.
let entries = cache
.iter()
.map(|(key, value)| FsCacheEntry { key: *key, value: value.clone() })
.collect::<Vec<_>>();
serde_json::to_writer(file, &entries).map_err(TransportErrorKind::custom)?;
Ok(())
}

/// Loads the cache from a file specified by the path.
/// If the file does not exist, it returns without error.
pub fn load_cache(&self, path: PathBuf) -> TransportResult<()> {
if !path.exists() {
return Ok(());
};
let file = std::fs::File::open(path).map_err(TransportErrorKind::custom)?;
let file = BufReader::new(file);
let entries: Vec<FsCacheEntry> =
serde_json::from_reader(file).map_err(TransportErrorKind::custom)?;
let mut cache = self.cache.write();
for entry in entries {
cache.put(entry.key, entry.value);
}

Ok(())
}
}

/// Attempts to fetch the response from the cache by using the hash of the request params.
Expand Down Expand Up @@ -184,8 +156,8 @@ macro_rules! cache_rpc_call_with_block {
let hash = $req.params_hash().ok();

if let Some(hash) = hash {
if let Some(cached) = $cache.write().get(&hash) {
let result = serde_json::from_str(cached).map_err(TransportErrorKind::custom);
if let Ok(Some(cached)) = $cache.get(&hash) {
let result = serde_json::from_str(&cached).map_err(TransportErrorKind::custom);
return ProviderCall::BoxedFuture(Box::pin(async move {
let res = result?;
Ok(res)
Expand Down Expand Up @@ -542,12 +514,38 @@ impl SharedCache {
Ok(val)
}

fn read(&self) -> parking_lot::RwLockReadGuard<'_, LruCache<B256, String>> {
self.inner.read()
/// Saves the cache to a file specified by the path.
/// If the files does not exist, it creates one.
/// If the file exists, it overwrites it.
pub fn save_cache(&self, path: PathBuf) -> TransportResult<()> {
let cache = self.inner.read();
yash-atreya marked this conversation as resolved.
Show resolved Hide resolved
let file = std::fs::File::create(path).map_err(TransportErrorKind::custom)?;

// Iterate over the cache and dump to the file.
let entries = cache
.iter()
.map(|(key, value)| FsCacheEntry { key: *key, value: value.clone() })
.collect::<Vec<_>>();
serde_json::to_writer(file, &entries).map_err(TransportErrorKind::custom)?;
Ok(())
}

fn write(&self) -> parking_lot::RwLockWriteGuard<'_, LruCache<B256, String>> {
self.inner.write()
/// Loads the cache from a file specified by the path.
/// If the file does not exist, it returns without error.
pub fn load_cache(&self, path: PathBuf) -> TransportResult<()> {
if !path.exists() {
return Ok(());
};
let file = std::fs::File::open(path).map_err(TransportErrorKind::custom)?;
let file = BufReader::new(file);
let entries: Vec<FsCacheEntry> =
serde_json::from_reader(file).map_err(TransportErrorKind::custom)?;
let mut cache = self.inner.write();
for entry in entries {
cache.put(entry.key, entry.value);
}

Ok(())
}
}

Expand All @@ -563,12 +561,13 @@ mod tests {
#[tokio::test]
async fn test_get_block() {
run_with_tempdir("get-block", |dir| async move {
let cache = CacheLayer::new(100);
let cache_layer = CacheLayer::new(100);
let shared_cache = cache_layer.cache();
let anvil = Anvil::new().block_time_f64(0.3).spawn();
let provider = ProviderBuilder::new().layer(cache).on_http(anvil.endpoint_url());
let provider = ProviderBuilder::new().layer(cache_layer).on_http(anvil.endpoint_url());

let path = dir.join("rpc-cache-block.txt");
provider.load_cache(path.clone()).unwrap();
shared_cache.load_cache(path.clone()).unwrap();

let block = provider.get_block(0.into(), BlockTransactionsKind::Full).await.unwrap(); // Received from RPC.
let block2 = provider.get_block(0.into(), BlockTransactionsKind::Full).await.unwrap(); // Received from cache.
Expand All @@ -586,23 +585,24 @@ mod tests {
provider.get_block_by_hash(latest_hash, BlockTransactionsKind::Full).await.unwrap(); // Received from cache.
assert_eq!(block3, block4);

provider.save_cache(path).unwrap();
shared_cache.save_cache(path).unwrap();
})
.await;
}

#[tokio::test]
async fn test_get_block_any_network() {
run_with_tempdir("get-block", |dir| async move {
let cache = CacheLayer::new(100);
let cache_layer = CacheLayer::new(100);
let shared_cache = cache_layer.cache();
let anvil = Anvil::new().block_time_f64(0.3).spawn();
let provider = ProviderBuilder::new()
.network::<AnyNetwork>()
.layer(cache)
.layer(cache_layer)
.on_http(anvil.endpoint_url());

let path = dir.join("rpc-cache-block.txt");
provider.load_cache(path.clone()).unwrap();
shared_cache.load_cache(path.clone()).unwrap();

let block = provider.get_block(0.into(), BlockTransactionsKind::Full).await.unwrap(); // Received from RPC.
let block2 = provider.get_block(0.into(), BlockTransactionsKind::Full).await.unwrap(); // Received from cache.
Expand All @@ -620,22 +620,23 @@ mod tests {
provider.get_block_by_hash(latest_hash, BlockTransactionsKind::Full).await.unwrap(); // Received from cache.
assert_eq!(block3, block4);

provider.save_cache(path).unwrap();
shared_cache.save_cache(path).unwrap();
})
.await;
}

#[tokio::test]
async fn test_get_proof() {
run_with_tempdir("get-proof", |dir| async move {
let cache = CacheLayer::new(100);
let cache_layer = CacheLayer::new(100);
let shared_cache = cache_layer.cache();
let anvil = Anvil::new().block_time_f64(0.3).spawn();
let provider = ProviderBuilder::new().layer(cache).on_http(anvil.endpoint_url());
let provider = ProviderBuilder::new().layer(cache_layer).on_http(anvil.endpoint_url());

let from = anvil.addresses()[0];
let path = dir.join("rpc-cache-proof.txt");

provider.load_cache(path.clone()).unwrap();
shared_cache.load_cache(path.clone()).unwrap();

let calldata: Bytes = "0x6080604052348015600f57600080fd5b506101f28061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633fb5c1cb146100465780638381f58a14610062578063d09de08a14610080575b600080fd5b610060600480360381019061005b91906100ee565b61008a565b005b61006a610094565b604051610077919061012a565b60405180910390f35b61008861009a565b005b8060008190555050565b60005481565b6000808154809291906100ac90610174565b9190505550565b600080fd5b6000819050919050565b6100cb816100b8565b81146100d657600080fd5b50565b6000813590506100e8816100c2565b92915050565b600060208284031215610104576101036100b3565b5b6000610112848285016100d9565b91505092915050565b610124816100b8565b82525050565b600060208201905061013f600083018461011b565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061017f826100b8565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101b1576101b0610145565b5b60018201905091905056fea264697066735822122067ac0f21f648b0cacd1b7260772852ad4a0f63e2cc174168c51a6887fd5197a964736f6c634300081a0033".parse().unwrap();

Expand Down Expand Up @@ -665,19 +666,20 @@ mod tests {

assert_eq!(proof, proof2);

provider.save_cache(path).unwrap();
shared_cache.save_cache(path).unwrap();
}).await;
}

#[tokio::test]
async fn test_get_tx_by_hash_and_receipt() {
run_with_tempdir("get-tx-by-hash", |dir| async move {
let cache = CacheLayer::new(100);
let cache_layer = CacheLayer::new(100);
let shared_cache = cache_layer.cache();
let anvil = Anvil::new().block_time_f64(0.3).spawn();
let provider = ProviderBuilder::new().layer(cache).on_http(anvil.endpoint_url());
let provider = ProviderBuilder::new().layer(cache_layer).on_http(anvil.endpoint_url());

let path = dir.join("rpc-cache-tx.txt");
provider.load_cache(path.clone()).unwrap();
shared_cache.load_cache(path.clone()).unwrap();

let req = TransactionRequest::default()
.from(anvil.addresses()[0])
Expand All @@ -697,20 +699,21 @@ mod tests {

assert_eq!(receipt, receipt2);

provider.save_cache(path).unwrap();
shared_cache.save_cache(path).unwrap();
})
.await;
}

#[tokio::test]
async fn test_block_receipts() {
run_with_tempdir("get-block-receipts", |dir| async move {
let cache = CacheLayer::new(100);
let cache_layer = CacheLayer::new(100);
let shared_cache = cache_layer.cache();
let anvil = Anvil::new().spawn();
let provider = ProviderBuilder::new().layer(cache).on_http(anvil.endpoint_url());
let provider = ProviderBuilder::new().layer(cache_layer).on_http(anvil.endpoint_url());

let path = dir.join("rpc-cache-block-receipts.txt");
provider.load_cache(path.clone()).unwrap();
shared_cache.load_cache(path.clone()).unwrap();

// Send txs

Expand All @@ -731,20 +734,21 @@ mod tests {

assert!(receipts.is_some_and(|r| r[0] == receipt));

provider.save_cache(path).unwrap();
shared_cache.save_cache(path).unwrap();
})
.await
}

#[tokio::test]
async fn test_get_code() {
run_with_tempdir("get-code", |dir| async move {
let cache = CacheLayer::new(100);
let cache_layer = CacheLayer::new(100);
let shared_cache = cache_layer.cache();
let anvil = Anvil::new().spawn();
let provider = ProviderBuilder::new().layer(cache).on_http(anvil.endpoint_url());
let provider = ProviderBuilder::new().layer(cache_layer).on_http(anvil.endpoint_url());

let path = dir.join("rpc-cache-code.txt");
provider.load_cache(path.clone()).unwrap();
shared_cache.load_cache(path.clone()).unwrap();

let bytecode = hex::decode(
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
Expand All @@ -762,7 +766,7 @@ mod tests {
let code2 = provider.get_code_at(counter_addr).block_id(block_id).await.unwrap(); // Received from cache.
assert_eq!(code, code2);

provider.save_cache(path).unwrap();
shared_cache.save_cache(path).unwrap();
})
.await;
}
Expand Down
Loading