Compare commits

...

1 Commits

Author SHA1 Message Date
Arsenii Kulikov
7a119da5f8 wip 2025-12-19 22:59:56 +04:00
6 changed files with 68 additions and 35 deletions

17
Cargo.lock generated
View File

@@ -3986,6 +3986,16 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "fixed-cache"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba59b6c98ba422a13f17ee1305c995cb5742bba7997f5b4d9af61b2ff0ffb213"
dependencies = [
"equivalent",
"typeid",
]
[[package]]
name = "fixed-hash"
version = "0.8.0"
@@ -8243,6 +8253,7 @@ dependencies = [
"dashmap 6.1.0",
"derive_more",
"eyre",
"fixed-cache",
"futures",
"metrics",
"metrics-util",
@@ -13202,6 +13213,12 @@ dependencies = [
"utf-8",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.19.0"

View File

@@ -588,6 +588,7 @@ zstd = "0.13"
byteorder = "1"
mini-moka = "0.10"
moka = "0.12"
fixed-cache = "0.1.3"
tar-no-std = { version = "0.3.2", default-features = false }
miniz_oxide = { version = "0.8.4", default-features = false }
chrono = "0.4.41"

View File

@@ -54,6 +54,7 @@ thiserror.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] }
mini-moka = { workspace = true, features = ["sync"] }
moka = { workspace = true, features = ["sync"] }
fixed-cache = { workspace = true, features = ["stats"] }
smallvec.workspace = true
# metrics

View File

@@ -438,7 +438,7 @@ where
precompile,
precompile_cache_map.cache_for_address(*address),
spec_id,
None, // No metrics for prewarm
false, // No metrics for prewarm
)
});
}

View File

@@ -5,7 +5,7 @@ use crate::tree::{
error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError},
instrumented_state::InstrumentedStateProvider,
payload_processor::{executor::WorkloadExecutor, PayloadProcessor},
precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap},
precompile_cache::{CachedPrecompile, PrecompileCacheMap},
sparse_trie::StateRootComputeOutcome,
EngineApiMetrics, EngineApiTreeState, ExecutionEnv, PayloadHandle, StateProviderBuilder,
StateProviderDatabase, TreeConfig,
@@ -45,7 +45,6 @@ use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot, TrieInputSorte
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
use revm_primitives::Address;
use std::{
collections::HashMap,
panic::{self, AssertUnwindSafe},
sync::Arc,
time::Instant,
@@ -121,8 +120,6 @@ where
payload_processor: PayloadProcessor<Evm>,
/// Precompile cache map.
precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
/// Precompile cache metrics.
precompile_cache_metrics: HashMap<alloy_primitives::Address, CachedPrecompileMetrics>,
/// Hook to call when invalid blocks are encountered.
#[debug(skip)]
invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
@@ -168,7 +165,6 @@ where
evm_config,
payload_processor,
precompile_cache_map,
precompile_cache_metrics: HashMap::new(),
config,
invalid_block_hook,
metrics: EngineApiMetrics::default(),
@@ -596,7 +592,7 @@ where
/// Executes a block with the given state provider
#[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
fn execute_block<S, Err, T>(
&mut self,
&self,
state_provider: S,
env: ExecutionEnv<Evm>,
input: &BlockOrPayload<T>,
@@ -625,16 +621,11 @@ where
if !self.config.precompile_cache_disabled() {
// Only cache pure precompiles to avoid issues with stateful precompiles
executor.evm_mut().precompiles_mut().map_pure_precompiles(|address, precompile| {
let metrics = self
.precompile_cache_metrics
.entry(*address)
.or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
.clone();
CachedPrecompile::wrap(
precompile,
self.precompile_cache_map.cache_for_address(*address),
*env.evm_env.spec_id(),
Some(metrics),
true,
)
});
}

View File

@@ -8,7 +8,7 @@ use revm_primitives::Address;
use std::{hash::Hash, sync::Arc};
/// Default max cache size for [`PrecompileCache`]
const MAX_CACHE_SIZE: u32 = 10_000;
const MAX_CACHE_SIZE: u32 = 131_072;
/// Stores caches for each precompile.
#[derive(Debug, Clone, Default)]
@@ -29,28 +29,33 @@ where
//
// This should be very rare as caches for all precompiles will be initialized as soon as
// first EVM is created.
self.0.entry(address).or_default().clone()
self.0.entry(address).or_insert_with(|| PrecompileCache::new(address)).clone()
}
}
/// Cache for precompiles, for each input stores the result.
#[derive(Debug, Clone)]
pub struct PrecompileCache<S>(
moka::sync::Cache<Bytes, CacheEntry<S>, alloy_primitives::map::DefaultHashBuilder>,
)
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static;
impl<S> Default for PrecompileCache<S>
pub struct PrecompileCache<S>
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
fn default() -> Self {
Self(
moka::sync::CacheBuilder::new(MAX_CACHE_SIZE as u64)
.initial_capacity(MAX_CACHE_SIZE as usize)
.build_with_hasher(Default::default()),
)
cache: Arc<fixed_cache::Cache<Bytes, CacheEntry<S>, alloy_primitives::map::DefaultHashBuilder>>,
metrics: CachedPrecompileMetrics,
}
impl<S> PrecompileCache<S>
where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
fn new(address: Address) -> Self {
let metrics = CachedPrecompileMetrics::new_with_address(address);
Self {
cache: Arc::new(
fixed_cache::Cache::new(MAX_CACHE_SIZE as usize, Default::default())
.with_stats(Some(fixed_cache::Stats::new(metrics.clone()))),
),
metrics,
}
}
}
@@ -59,13 +64,16 @@ where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
fn get(&self, input: &[u8], spec: S) -> Option<CacheEntry<S>> {
self.0.get(input).filter(|e| e.spec == spec)
self.cache
.get(input)
.filter(|e| e.spec == spec)
.inspect(|_| self.metrics.precompile_cache_hits.increment(1))
}
/// Inserts the given key and value into the cache, returning the new cache size.
fn insert(&self, input: Bytes, value: CacheEntry<S>) -> usize {
self.0.insert(input, value);
self.0.entry_count() as usize
self.cache.insert(input, value);
MAX_CACHE_SIZE as usize
}
}
@@ -107,12 +115,13 @@ where
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
{
/// `CachedPrecompile` constructor.
pub(crate) const fn new(
pub(crate) fn new(
precompile: DynPrecompile,
cache: PrecompileCache<S>,
spec_id: S,
metrics: Option<CachedPrecompileMetrics>,
track_hits_and_misses: bool,
) -> Self {
let metrics = track_hits_and_misses.then(|| cache.metrics.clone());
Self { precompile, cache, spec_id, metrics }
}
@@ -120,10 +129,10 @@ where
precompile: DynPrecompile,
cache: PrecompileCache<S>,
spec_id: S,
metrics: Option<CachedPrecompileMetrics>,
track_hits_and_misses: bool,
) -> DynPrecompile {
let precompile_id = precompile.precompile_id().clone();
let wrapped = Self::new(precompile, cache, spec_id, metrics);
let wrapped = Self::new(precompile, cache, spec_id, track_hits_and_misses);
(precompile_id, move |input: PrecompileInput<'_>| -> PrecompileResult {
wrapped.call(input)
})
@@ -204,6 +213,9 @@ pub(crate) struct CachedPrecompileMetrics {
/// Precompile cache size. Uses the LRU cache length as the size metric.
precompile_cache_size: metrics::Gauge,
/// Precompile cache collisions.
precompile_cache_collisions: metrics::Counter,
/// Precompile execution errors.
precompile_errors: metrics::Counter,
}
@@ -218,6 +230,17 @@ impl CachedPrecompileMetrics {
}
}
impl<S> fixed_cache::StatsHandler<Bytes, CacheEntry<S>> for CachedPrecompileMetrics {
fn on_collision(
&self,
_new_key: fixed_cache::AnyRef<'_>,
_existing_key: &Bytes,
_existing_value: &CacheEntry<S>,
) {
self.precompile_cache_collisions.increment(1);
}
}
#[cfg(test)]
mod tests {
use super::*;