Skip to content

Commit f627340

Browse files
feat: execution data caching for EVM (#771)
* refactor: use RpcBlockHash in ProofDB for type-safe block pinning * feat: state and storage cache for EVM - Implement global execution data cache accessible synchronously by EVM - Avoid unconditional get_execution_hint call by EVM, check cache first * fix: fmt and clippy * decrease cache sizes * fix: dont leak cache into ProofDB, keep abstraction within AccountProvider * fix: add LRU for slots per storage instead of unbounded, decrease limits * remove get_verified_code method from account provider * remove caching provider, handle cache directly in RPC provider * Revert "remove caching provider, handle cache directly in RPC provider" This reverts commit ebc5524. * refactor get_account method in cache provider
1 parent 582fda3 commit f627340

File tree

14 files changed

+692
-21
lines changed

14 files changed

+692
-21
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ impl OpStackArgs {
271271
user_dict.insert("consensus_rpc", Value::from(rpc.to_string()));
272272
}
273273

274-
if self.rpc_bind_ip.is_some() && self.rpc_port.is_some() {
275-
let rpc_socket = SocketAddr::new(self.rpc_bind_ip.unwrap(), self.rpc_port.unwrap());
274+
if let (Some(ip), Some(port)) = (self.rpc_bind_ip, self.rpc_port) {
275+
let rpc_socket = SocketAddr::new(ip, port);
276276
user_dict.insert("rpc_socket", Value::from(rpc_socket.to_string()));
277277
}
278278

core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ reqwest.workspace = true
2323
serde.workspace = true
2424
serde_json.workspace = true
2525

26+
# cache
27+
schnellru = "0.2"
28+
2629
# misc
2730
eyre.workspace = true
2831
hex.workspace = true

core/src/execution/cache/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod provider;
2+
mod state;
3+
4+
pub use provider::CachingProvider;
5+
pub(crate) use state::Cache;
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
use std::{collections::HashMap, sync::Arc};
2+
3+
use alloy::{
4+
eips::BlockId,
5+
network::{primitives::HeaderResponse, BlockResponse},
6+
primitives::{Address, B256},
7+
rpc::types::{Filter, Log},
8+
};
9+
use async_trait::async_trait;
10+
use eyre::{eyre, Result};
11+
12+
use helios_common::{
13+
execution_provider::{
14+
AccountProvider, BlockProvider, ExecutionHintProvider, ExecutionProvider, LogProvider,
15+
ReceiptProvider, TransactionProvider,
16+
},
17+
network_spec::NetworkSpec,
18+
types::Account,
19+
};
20+
21+
use super::Cache;
22+
23+
pub struct CachingProvider<P> {
24+
inner: P,
25+
cache: Arc<Cache>,
26+
}
27+
28+
impl<P> CachingProvider<P> {
29+
pub fn new(inner: P) -> Self {
30+
Self {
31+
inner,
32+
cache: Arc::new(Cache::new()),
33+
}
34+
}
35+
}
36+
37+
impl<N, P> ExecutionProvider<N> for CachingProvider<P>
38+
where
39+
N: NetworkSpec,
40+
P: ExecutionProvider<N>,
41+
{
42+
}
43+
44+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
45+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
46+
impl<N, P> AccountProvider<N> for CachingProvider<P>
47+
where
48+
N: NetworkSpec,
49+
P: ExecutionProvider<N>,
50+
{
51+
async fn get_account(
52+
&self,
53+
address: Address,
54+
slots: &[B256],
55+
with_code: bool,
56+
block_id: BlockId,
57+
) -> Result<Account> {
58+
let block_hash = match block_id {
59+
BlockId::Hash(hash) => hash.into(),
60+
_ => self
61+
.inner
62+
.get_block(block_id, false)
63+
.await?
64+
.ok_or_else(|| eyre!("block not found"))?
65+
.header()
66+
.hash(),
67+
};
68+
69+
let cached = self.cache.get_account(address, slots, block_hash);
70+
71+
match cached {
72+
None => {
73+
if with_code {
74+
if let Some((cached_code_hash, cached_code)) =
75+
self.cache.get_code_optimistically(address)
76+
{
77+
let fetched = self
78+
.inner
79+
.get_account(address, slots, false, block_hash.into())
80+
.await?;
81+
if fetched.account.code_hash == cached_code_hash {
82+
let mut account = fetched;
83+
account.code = Some(cached_code);
84+
self.cache.insert_account(address, &account, block_hash);
85+
return Ok(account);
86+
}
87+
}
88+
}
89+
let fetched = self
90+
.inner
91+
.get_account(address, slots, with_code, block_hash.into())
92+
.await?;
93+
self.cache.insert_account(address, &fetched, block_hash);
94+
Ok(fetched)
95+
}
96+
Some((mut account, missing_slots)) => {
97+
let need_code = with_code && account.code.is_none();
98+
99+
if !missing_slots.is_empty() || need_code {
100+
let fetched = self
101+
.inner
102+
.get_account(address, &missing_slots, need_code, block_hash.into())
103+
.await?;
104+
self.cache.insert_account(address, &fetched, block_hash);
105+
106+
account.account = fetched.account;
107+
account.account_proof = fetched.account_proof;
108+
account.storage_proof.extend(fetched.storage_proof);
109+
if let Some(code) = fetched.code {
110+
account.code = Some(code);
111+
}
112+
}
113+
114+
Ok(account)
115+
}
116+
}
117+
}
118+
}
119+
120+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
121+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
122+
impl<N, P> BlockProvider<N> for CachingProvider<P>
123+
where
124+
N: NetworkSpec,
125+
P: ExecutionProvider<N>,
126+
{
127+
async fn push_block(&self, block: N::BlockResponse, block_id: BlockId) {
128+
self.inner.push_block(block, block_id).await
129+
}
130+
131+
async fn get_block(
132+
&self,
133+
block_id: BlockId,
134+
full_tx: bool,
135+
) -> Result<Option<N::BlockResponse>> {
136+
self.inner.get_block(block_id, full_tx).await
137+
}
138+
139+
async fn get_untrusted_block(
140+
&self,
141+
block_id: BlockId,
142+
full_tx: bool,
143+
) -> Result<Option<N::BlockResponse>> {
144+
self.inner.get_untrusted_block(block_id, full_tx).await
145+
}
146+
}
147+
148+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
149+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
150+
impl<N, P> TransactionProvider<N> for CachingProvider<P>
151+
where
152+
N: NetworkSpec,
153+
P: ExecutionProvider<N>,
154+
{
155+
async fn send_raw_transaction(&self, bytes: &[u8]) -> Result<B256> {
156+
self.inner.send_raw_transaction(bytes).await
157+
}
158+
159+
async fn get_transaction(&self, hash: B256) -> Result<Option<N::TransactionResponse>> {
160+
self.inner.get_transaction(hash).await
161+
}
162+
163+
async fn get_transaction_by_location(
164+
&self,
165+
block_id: BlockId,
166+
index: u64,
167+
) -> Result<Option<N::TransactionResponse>> {
168+
self.inner
169+
.get_transaction_by_location(block_id, index)
170+
.await
171+
}
172+
}
173+
174+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
175+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
176+
impl<N, P> ReceiptProvider<N> for CachingProvider<P>
177+
where
178+
N: NetworkSpec,
179+
P: ExecutionProvider<N>,
180+
{
181+
async fn get_receipt(&self, hash: B256) -> Result<Option<N::ReceiptResponse>> {
182+
self.inner.get_receipt(hash).await
183+
}
184+
185+
async fn get_block_receipts(&self, block: BlockId) -> Result<Option<Vec<N::ReceiptResponse>>> {
186+
self.inner.get_block_receipts(block).await
187+
}
188+
}
189+
190+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
191+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
192+
impl<N, P> LogProvider<N> for CachingProvider<P>
193+
where
194+
N: NetworkSpec,
195+
P: ExecutionProvider<N>,
196+
{
197+
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>> {
198+
self.inner.get_logs(filter).await
199+
}
200+
}
201+
202+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
203+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
204+
impl<N, P> ExecutionHintProvider<N> for CachingProvider<P>
205+
where
206+
N: NetworkSpec,
207+
P: ExecutionProvider<N>,
208+
{
209+
async fn get_execution_hint(
210+
&self,
211+
call: &N::TransactionRequest,
212+
validate: bool,
213+
block_id: BlockId,
214+
) -> Result<HashMap<Address, Account>> {
215+
self.inner
216+
.get_execution_hint(call, validate, block_id)
217+
.await
218+
}
219+
}

0 commit comments

Comments
 (0)