Skip to content

Commit 2bc3398

Browse files
authored
refactor(executor): use global class cache always (#100)
* refactor(executor): use global class cache always * fix: build global cache * add unit tests
1 parent 4200cb0 commit 2bc3398

File tree

14 files changed

+158
-54
lines changed

14 files changed

+158
-54
lines changed

Cargo.lock

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

crates/chain-spec/src/rollup/utils.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ impl<'c> GenesisTransactionsBuilder<'c> {
339339
mod tests {
340340

341341
use alloy_primitives::U256;
342+
use katana_executor::implementation::blockifier::cache::ClassCache;
342343
use katana_executor::implementation::blockifier::BlockifierFactory;
343344
use katana_executor::{BlockLimits, ExecutorFactory};
344345
use katana_primitives::chain::ChainId;
@@ -400,6 +401,7 @@ mod tests {
400401
},
401402
Default::default(),
402403
BlockLimits::default(),
404+
ClassCache::new().unwrap(),
403405
)
404406
}
405407

crates/core/tests/backend.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use katana_chain_spec::{dev, ChainSpec, SettlementLayer};
44
use katana_core::backend::gas_oracle::GasOracle;
55
use katana_core::backend::storage::{Blockchain, Database};
66
use katana_core::backend::Backend;
7+
use katana_executor::implementation::blockifier::cache::ClassCache;
78
use katana_executor::implementation::blockifier::BlockifierFactory;
89
use katana_executor::BlockLimits;
910
use katana_primitives::chain::ChainId;
@@ -27,6 +28,7 @@ fn executor(chain_spec: &ChainSpec) -> BlockifierFactory {
2728
},
2829
Default::default(),
2930
BlockLimits::default(),
31+
ClassCache::new().unwrap(),
3032
)
3133
}
3234

crates/executor/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ katana-rpc-types.workspace = true
3030

3131
alloy-primitives.workspace = true
3232
anyhow.workspace = true
33+
assert_matches.workspace = true
3334
num-traits.workspace = true
3435
rstest.workspace = true
3536
rstest_reuse.workspace = true

crates/executor/benches/concurrent.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::time::Duration;
88

99
use criterion::measurement::WallTime;
1010
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkGroup, Criterion};
11+
use katana_executor::implementation::blockifier::cache::ClassCache;
1112
use katana_executor::implementation::blockifier::BlockifierFactory;
1213
use katana_executor::{BlockLimits, ExecutionFlags, ExecutorFactory};
1314
use katana_primitives::env::{BlockEnv, CfgEnv};
@@ -44,7 +45,12 @@ fn blockifier(
4445
(block_env, cfg_env): (BlockEnv, CfgEnv),
4546
tx: ExecutableTxWithHash,
4647
) {
47-
let factory = Arc::new(BlockifierFactory::new(cfg_env, flags.clone(), BlockLimits::default()));
48+
let factory = Arc::new(BlockifierFactory::new(
49+
cfg_env,
50+
flags.clone(),
51+
BlockLimits::default(),
52+
ClassCache::new().unwrap(),
53+
));
4854

4955
group.bench_function("Blockifier.1", |b| {
5056
b.iter_batched(

crates/executor/benches/execution.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::time::Duration;
33
use blockifier::state::cached_state::CachedState;
44
use criterion::measurement::WallTime;
55
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkGroup, Criterion};
6-
use katana_executor::implementation::blockifier::cache::ClassCache;
76
use katana_executor::implementation::blockifier::state::StateProviderDb;
87
use katana_executor::ExecutionFlags;
98
use katana_primitives::env::{BlockEnv, CfgEnv};
@@ -47,8 +46,7 @@ fn blockifier(
4746
|| {
4847
// setup state
4948
let state = provider.latest().expect("failed to get latest state");
50-
let class_cache = ClassCache::new().unwrap();
51-
let state = CachedState::new(StateProviderDb::new(state, class_cache));
49+
let state = CachedState::new(StateProviderDb::new(state));
5250

5351
(state, &block_context, execution_flags, tx.clone())
5452
},

crates/executor/src/implementation/blockifier/cache.rs

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::str::FromStr;
2-
use std::sync::{Arc, LazyLock};
2+
use std::sync::{Arc, OnceLock};
33

44
use blockifier::execution::contract_class::{CompiledClassV1, RunnableCompiledClass};
55
use katana_primitives::class::{ClassHash, CompiledClass, ContractClass};
@@ -8,11 +8,16 @@ use starknet_api::contract_class::SierraVersion;
88

99
use super::utils::to_class;
1010

11-
pub static COMPILED_CLASS_CACHE: LazyLock<ClassCache> =
12-
LazyLock::new(|| ClassCache::builder().build().unwrap());
11+
static COMPILED_CLASS_CACHE: OnceLock<ClassCache> = OnceLock::new();
1312

1413
#[derive(Debug, thiserror::Error)]
1514
pub enum Error {
15+
#[error("global class cache already initialized.")]
16+
AlreadyInitialized,
17+
18+
#[error("global class cache not initialized.")]
19+
NotInitialized,
20+
1621
#[cfg(feature = "native")]
1722
#[error(transparent)]
1823
FailedToCreateThreadPool(#[from] rayon::ThreadPoolBuildError),
@@ -126,6 +131,20 @@ impl ClassCacheBuilder {
126131
}),
127132
})
128133
}
134+
135+
/// Builds a new `ClassCache` instance and sets it as the global cache.
136+
///
137+
/// This builds and initializes a global `ClassCache` that can be accessed via
138+
/// [`ClassCache::global`].
139+
///
140+
/// ## Errors
141+
///
142+
/// Returns an error if the global cache has already been initialized.
143+
pub fn build_global(self) -> Result<ClassCache, Error> {
144+
let cache = self.build()?;
145+
COMPILED_CLASS_CACHE.set(cache.clone()).map_err(|_| Error::AlreadyInitialized)?;
146+
Ok(cache)
147+
}
129148
}
130149

131150
impl std::fmt::Debug for ClassCacheBuilder {
@@ -193,6 +212,23 @@ impl ClassCache {
193212
ClassCacheBuilder::new()
194213
}
195214

215+
/// Returns a reference to the global cache instance.
216+
///
217+
/// This method will return an error if the global cache has not been initialized via
218+
/// [`ClassCacheBuilder::build_global`] first.
219+
pub fn try_global() -> Result<&'static ClassCache, Error> {
220+
COMPILED_CLASS_CACHE.get().ok_or(Error::NotInitialized)
221+
}
222+
223+
/// Returns a reference to the global cache instance.
224+
///
225+
/// # Panics
226+
///
227+
/// Panics if the global cache has not been initialized.
228+
pub fn global() -> &'static ClassCache {
229+
Self::try_global().expect("global class cache not initialized")
230+
}
231+
196232
pub fn get(&self, hash: &ClassHash) -> Option<RunnableCompiledClass> {
197233
self.inner.cache.get(hash)
198234
}
@@ -254,3 +290,55 @@ impl ClassCache {
254290
}
255291
}
256292
}
293+
294+
#[cfg(test)]
295+
mod tests {
296+
use assert_matches::assert_matches;
297+
use katana_primitives::felt;
298+
use katana_primitives::genesis::constant::{DEFAULT_ACCOUNT_CLASS, DEFAULT_LEGACY_UDC_CLASS};
299+
300+
use super::{ClassCache, ClassCacheBuilder, Error};
301+
302+
#[test]
303+
fn independent_cache() {
304+
let cache1 = ClassCacheBuilder::new().build().expect("Failed to build cache 1");
305+
let cache2 = ClassCacheBuilder::new().build().expect("Failed to build cache 2");
306+
307+
let class_hash1 = felt!("0x1");
308+
let class_hash2 = felt!("0x2");
309+
310+
cache1.insert(class_hash1, DEFAULT_ACCOUNT_CLASS.clone());
311+
cache1.insert(class_hash2, DEFAULT_LEGACY_UDC_CLASS.clone());
312+
313+
assert!(cache1.get(&class_hash1).is_some());
314+
assert!(cache1.get(&class_hash2).is_some());
315+
assert!(cache2.get(&class_hash1).is_none());
316+
assert!(cache2.get(&class_hash2).is_none());
317+
}
318+
319+
#[test]
320+
fn global_cache() {
321+
// Can't get global without initializing it first
322+
let error = ClassCache::try_global().unwrap_err();
323+
assert_matches!(error, Error::NotInitialized, "Global cache not initialized");
324+
325+
let cache1 = ClassCacheBuilder::new().build_global().expect("failed to build global cache");
326+
327+
let error = ClassCacheBuilder::new().build_global().unwrap_err();
328+
assert_matches!(error, Error::AlreadyInitialized, "Global cache already initialized");
329+
330+
// Check that calling ClassCache::global() returns the same instance as cache1
331+
let cache2 = ClassCache::global();
332+
333+
let class_hash1 = felt!("0x1");
334+
let class_hash2 = felt!("0x2");
335+
336+
cache1.insert(class_hash1, DEFAULT_ACCOUNT_CLASS.clone());
337+
cache1.insert(class_hash2, DEFAULT_LEGACY_UDC_CLASS.clone());
338+
339+
assert!(cache1.get(&class_hash1).is_some());
340+
assert!(cache1.get(&class_hash2).is_some());
341+
assert!(cache2.get(&class_hash1).is_some());
342+
assert!(cache2.get(&class_hash2).is_some());
343+
}
344+
}

crates/executor/src/implementation/blockifier/call.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ mod tests {
118118
use starknet::macros::selector;
119119

120120
use super::execute_call_inner;
121-
use crate::implementation::blockifier::cache::ClassCache;
122121
use crate::implementation::blockifier::state::StateProviderDb;
123122
use crate::EntryPointCall;
124123

@@ -141,8 +140,7 @@ mod tests {
141140
provider.set_class_hash_of_contract(address, class_hash).unwrap();
142141

143142
let state = provider.latest().unwrap();
144-
let cache = ClassCache::new().expect("failed to create ClassCache");
145-
let state = StateProviderDb::new(state, cache);
143+
let state = StateProviderDb::new(state);
146144

147145
// ---------------------------------------------------------------
148146

@@ -218,8 +216,7 @@ mod tests {
218216
provider.set_class_hash_of_contract(address, class_hash).unwrap();
219217

220218
let state = provider.latest().unwrap();
221-
let cache = ClassCache::new().expect("failed to create ClassCache");
222-
let state = StateProviderDb::new(state, cache);
219+
let state = StateProviderDb::new(state);
223220

224221
// ---------------------------------------------------------------
225222

crates/executor/src/implementation/blockifier/mod.rs

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod utils;
1313
use blockifier::context::BlockContext;
1414
use blockifier::state::cached_state::{self, MutRefState};
1515
use blockifier::state::state_api::StateReader;
16+
use cache::ClassCache;
1617
use katana_primitives::block::{ExecutableBlock, GasPrice as KatanaGasPrices, PartialHeader};
1718
use katana_primitives::env::{BlockEnv, CfgEnv};
1819
use katana_primitives::fee::TxFeeInfo;
@@ -39,27 +40,18 @@ pub struct BlockifierFactory {
3940
flags: ExecutionFlags,
4041
limits: BlockLimits,
4142
max_call_gas: u64,
42-
#[cfg(feature = "native")]
43-
use_cairo_native: bool,
43+
class_cache: ClassCache,
4444
}
4545

4646
impl BlockifierFactory {
4747
/// Create a new factory with the given configuration and simulation flags.
48-
pub fn new(cfg: CfgEnv, flags: ExecutionFlags, limits: BlockLimits) -> Self {
49-
Self {
50-
cfg,
51-
flags,
52-
limits,
53-
#[cfg(feature = "native")]
54-
use_cairo_native: false,
55-
max_call_gas: 1_000_000_000,
56-
}
57-
}
58-
59-
#[cfg(feature = "native")]
60-
pub fn cairo_native(&mut self, enable: bool) -> &mut Self {
61-
self.use_cairo_native = enable;
62-
self
48+
pub fn new(
49+
cfg: CfgEnv,
50+
flags: ExecutionFlags,
51+
limits: BlockLimits,
52+
class_cache: ClassCache,
53+
) -> Self {
54+
Self { cfg, flags, limits, max_call_gas: 1_000_000_000, class_cache }
6355
}
6456

6557
pub fn set_max_call_gas(&mut self, max_call_gas: u64) {
@@ -93,8 +85,7 @@ impl ExecutorFactory for BlockifierFactory {
9385
flags,
9486
limits,
9587
self.max_call_gas,
96-
#[cfg(feature = "native")]
97-
self.use_cairo_native,
88+
self.class_cache.clone(),
9889
))
9990
}
10091

@@ -127,18 +118,12 @@ impl<'a> StarknetVMProcessor<'a> {
127118
simulation_flags: ExecutionFlags,
128119
limits: BlockLimits,
129120
max_call_gas: u64,
130-
#[cfg(feature = "native")] cairo_native: bool,
121+
class_cache: ClassCache,
131122
) -> Self {
132123
let transactions = Vec::new();
133124
let block_context = Arc::new(utils::block_context_from_envs(&block_env, &cfg_env));
134-
let cache_builder = cache::ClassCache::builder();
135-
136-
#[cfg(not(feature = "native"))]
137-
let compiled_class_cache = cache_builder.build().unwrap();
138-
#[cfg(feature = "native")]
139-
let compiled_class_cache = cache_builder.compile_native(cairo_native).build().unwrap();
140125

141-
let state = state::CachedState::new(state, compiled_class_cache);
126+
let state = state::CachedState::new(state, class_cache);
142127

143128
let mut block_max_capacity = BouncerWeights::max();
144129

crates/executor/src/implementation/blockifier/state.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ pub(crate) struct CachedStateInner<'a> {
3232
}
3333

3434
impl<'a> CachedState<'a> {
35-
pub(super) fn new(state: impl StateProvider + 'a, compiled_class_cache: ClassCache) -> Self {
36-
let state = StateProviderDb::new(Box::new(state), compiled_class_cache);
35+
pub(super) fn new(state: impl StateProvider + 'a, class_cache: ClassCache) -> Self {
36+
let state = StateProviderDb::new_with_class_cache(Box::new(state), class_cache);
3737
let cached_state = cached_state::CachedState::new(state);
3838

3939
let declared_classes = HashMap::new();
@@ -152,9 +152,19 @@ impl<'a> Deref for StateProviderDb<'a> {
152152
}
153153

154154
impl<'a> StateProviderDb<'a> {
155-
pub fn new(provider: Box<dyn StateProvider + 'a>, compiled_class_cache: ClassCache) -> Self {
155+
/// Creates a new [`StateProviderDb`].
156+
pub fn new(provider: Box<dyn StateProvider + 'a>) -> Self {
157+
let compiled_class_cache = ClassCache::new().expect("failed to build class cache");
156158
Self { provider, compiled_class_cache }
157159
}
160+
161+
/// Creates a new [`StateProviderDb`] with a custom class cache.
162+
pub fn new_with_class_cache(
163+
provider: Box<dyn StateProvider + 'a>,
164+
class_cache: ClassCache,
165+
) -> Self {
166+
Self { provider, compiled_class_cache: class_cache }
167+
}
158168
}
159169

160170
impl StateReader for StateProviderDb<'_> {

crates/executor/tests/fixtures/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,14 @@ pub fn executor_factory<EF: ExecutorFactory>(
270270

271271
#[cfg(feature = "blockifier")]
272272
pub mod blockifier {
273+
use katana_executor::implementation::blockifier::cache::ClassCache;
273274
use katana_executor::implementation::blockifier::BlockifierFactory;
274275
use katana_executor::{BlockLimits, ExecutionFlags};
275276

276277
use super::{cfg, flags, CfgEnv};
277278

278279
#[rstest::fixture]
279280
pub fn factory(cfg: CfgEnv, #[with(true)] flags: ExecutionFlags) -> BlockifierFactory {
280-
BlockifierFactory::new(cfg, flags, BlockLimits::default())
281+
BlockifierFactory::new(cfg, flags, BlockLimits::default(), ClassCache::new().unwrap())
281282
}
282283
}

crates/node/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ version.workspace = true
99
katana-chain-spec.workspace = true
1010
katana-core.workspace = true
1111
katana-db.workspace = true
12-
katana-executor.workspace = true
12+
katana-executor = { workspace = true, features = [ "blockifier" ] }
1313
katana-explorer.workspace = true
1414
katana-messaging.workspace = true
1515
katana-metrics.workspace = true

0 commit comments

Comments
 (0)