feat: add precompile cache for execution (#15928)

Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Federico Gimenez
2025-05-09 11:36:28 +02:00
committed by GitHub
parent a3c067c2b2
commit 2054a37b02
24 changed files with 663 additions and 439 deletions

544
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -444,24 +444,24 @@ reth-ress-protocol = { path = "crates/ress/protocol" }
reth-ress-provider = { path = "crates/ress/provider" } reth-ress-provider = { path = "crates/ress/provider" }
# revm # revm
revm = { version = "22.0.1", default-features = false } revm = { version = "23.1.0", default-features = false }
revm-bytecode = { version = "3.0.0", default-features = false } revm-bytecode = { version = "4.0.0", default-features = false }
revm-state = { version = "=3.0.0", default-features = false } revm-database = { version = "4.0.0", default-features = false }
revm-primitives = { version = "18.0.0", default-features = false } revm-state = { version = "4.0.0", default-features = false }
revm-interpreter = { version = "18.0.0", default-features = false } revm-primitives = { version = "19.0.0", default-features = false }
revm-inspector = { version = "3.0.0", default-features = false } revm-interpreter = { version = "19.0.0", default-features = false }
revm-context = { version = "3.0.0", default-features = false } revm-inspector = { version = "4.0.0", default-features = false }
revm-context-interface = { version = "3.0.0", default-features = false } revm-context = { version = "4.0.0", default-features = false }
revm-database = { version = "=3.0.0", default-features = false } revm-context-interface = { version = "4.0.0", default-features = false }
revm-database-interface = { version = "=3.0.0", default-features = false } revm-database-interface = { version = "4.0.0", default-features = false }
op-revm = { version = "=3.0.2", default-features = false } op-revm = { version = "4.0.0", default-features = false }
revm-inspectors = "0.20.0" revm-inspectors = "0.21.0"
# eth # eth
alloy-chains = { version = "0.2.0", default-features = false } alloy-chains = { version = "0.2.0", default-features = false }
alloy-dyn-abi = "1.1.0" alloy-dyn-abi = "1.1.0"
alloy-eip2124 = { version = "0.2.0", default-features = false } alloy-eip2124 = { version = "0.2.0", default-features = false }
alloy-evm = { version = "0.6.0", default-features = false } alloy-evm = { version = "0.7.0", default-features = false }
alloy-primitives = { version = "1.1.0", default-features = false, features = ["map-foldhash"] } alloy-primitives = { version = "1.1.0", default-features = false, features = ["map-foldhash"] }
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
alloy-sol-macro = "1.1.0" alloy-sol-macro = "1.1.0"
@@ -499,7 +499,7 @@ alloy-transport-ipc = { version = "0.15.10", default-features = false }
alloy-transport-ws = { version = "0.15.10", default-features = false } alloy-transport-ws = { version = "0.15.10", default-features = false }
# op # op
alloy-op-evm = { version = "0.6.0", default-features = false } alloy-op-evm = { version = "0.7.0", default-features = false }
alloy-op-hardforks = "0.2.0" alloy-op-hardforks = "0.2.0"
op-alloy-rpc-types = { version = "0.15.4", default-features = false } op-alloy-rpc-types = { version = "0.15.4", default-features = false }
op-alloy-rpc-types-engine = { version = "0.15.4", default-features = false } op-alloy-rpc-types-engine = { version = "0.15.4", default-features = false }

View File

@@ -764,6 +764,9 @@ Engine:
[default: 1] [default: 1]
--engine.precompile-cache
Enable precompile cache
Ress: Ress:
--ress.enable --ress.enable
Enable support for `ress` subprotocol Enable support for `ress` subprotocol

View File

@@ -75,6 +75,8 @@ pub struct TreeConfig {
max_proof_task_concurrency: u64, max_proof_task_concurrency: u64,
/// Number of reserved CPU cores for non-reth processes /// Number of reserved CPU cores for non-reth processes
reserved_cpu_cores: usize, reserved_cpu_cores: usize,
/// Whether to enable the precompile cache
precompile_cache_enabled: bool,
} }
impl Default for TreeConfig { impl Default for TreeConfig {
@@ -93,6 +95,7 @@ impl Default for TreeConfig {
has_enough_parallelism: has_enough_parallelism(), has_enough_parallelism: has_enough_parallelism(),
max_proof_task_concurrency: DEFAULT_MAX_PROOF_TASK_CONCURRENCY, max_proof_task_concurrency: DEFAULT_MAX_PROOF_TASK_CONCURRENCY,
reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES, reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
precompile_cache_enabled: false,
} }
} }
} }
@@ -114,6 +117,7 @@ impl TreeConfig {
has_enough_parallelism: bool, has_enough_parallelism: bool,
max_proof_task_concurrency: u64, max_proof_task_concurrency: u64,
reserved_cpu_cores: usize, reserved_cpu_cores: usize,
precompile_cache_enabled: bool,
) -> Self { ) -> Self {
Self { Self {
persistence_threshold, persistence_threshold,
@@ -129,6 +133,7 @@ impl TreeConfig {
has_enough_parallelism, has_enough_parallelism,
max_proof_task_concurrency, max_proof_task_concurrency,
reserved_cpu_cores, reserved_cpu_cores,
precompile_cache_enabled,
} }
} }
@@ -189,11 +194,16 @@ impl TreeConfig {
self.always_compare_trie_updates self.always_compare_trie_updates
} }
/// Return the cross-block cache size. /// Returns the cross-block cache size.
pub const fn cross_block_cache_size(&self) -> u64 { pub const fn cross_block_cache_size(&self) -> u64 {
self.cross_block_cache_size self.cross_block_cache_size
} }
/// Returns whether precompile cache is enabled.
pub const fn precompile_cache_enabled(&self) -> bool {
self.precompile_cache_enabled
}
/// Setter for persistence threshold. /// Setter for persistence threshold.
pub const fn with_persistence_threshold(mut self, persistence_threshold: u64) -> Self { pub const fn with_persistence_threshold(mut self, persistence_threshold: u64) -> Self {
self.persistence_threshold = persistence_threshold; self.persistence_threshold = persistence_threshold;
@@ -291,6 +301,12 @@ impl TreeConfig {
self self
} }
/// Setter for whether to use the precompile cache.
pub const fn with_precompile_cache_enabled(mut self, precompile_cache_enabled: bool) -> Self {
self.precompile_cache_enabled = precompile_cache_enabled;
self
}
/// Whether or not to use state root task /// Whether or not to use state root task
pub const fn use_state_root_task(&self) -> bool { pub const fn use_state_root_task(&self) -> bool {
self.has_enough_parallelism && !self.legacy_state_root self.has_enough_parallelism && !self.legacy_state_root

View File

@@ -42,6 +42,7 @@ alloy-primitives.workspace = true
alloy-rlp.workspace = true alloy-rlp.workspace = true
alloy-rpc-types-engine.workspace = true alloy-rpc-types-engine.workspace = true
revm.workspace = true
revm-primitives.workspace = true revm-primitives.workspace = true
# common # common

View File

@@ -13,7 +13,8 @@ use reth_chain_state::EthPrimitives;
use reth_chainspec::ChainSpec; use reth_chainspec::ChainSpec;
use reth_db_common::init::init_genesis; use reth_db_common::init::init_genesis;
use reth_engine_tree::tree::{ use reth_engine_tree::tree::{
executor::WorkloadExecutor, PayloadProcessor, StateProviderBuilder, TreeConfig, executor::WorkloadExecutor, precompile_cache::PrecompileCache, PayloadProcessor,
StateProviderBuilder, TreeConfig,
}; };
use reth_evm::OnStateHook; use reth_evm::OnStateHook;
use reth_evm_ethereum::EthEvmConfig; use reth_evm_ethereum::EthEvmConfig;
@@ -220,6 +221,7 @@ fn bench_state_root(c: &mut Criterion) {
WorkloadExecutor::default(), WorkloadExecutor::default(),
EthEvmConfig::new(factory.chain_spec()), EthEvmConfig::new(factory.chain_spec()),
&TreeConfig::default(), &TreeConfig::default(),
PrecompileCache::default(),
); );
let provider = BlockchainProvider::new(factory).unwrap(); let provider = BlockchainProvider::new(factory).unwrap();

View File

@@ -9,6 +9,7 @@ use crate::{
}; };
use alloy_consensus::BlockHeader; use alloy_consensus::BlockHeader;
use alloy_eips::{merge::EPOCH_SLOTS, BlockNumHash, NumHash}; use alloy_eips::{merge::EPOCH_SLOTS, BlockNumHash, NumHash};
use alloy_evm::block::BlockExecutor;
use alloy_primitives::{ use alloy_primitives::{
map::{HashMap, HashSet}, map::{HashMap, HashSet},
BlockNumber, B256, BlockNumber, B256,
@@ -20,6 +21,7 @@ use error::{InsertBlockError, InsertBlockErrorKind, InsertBlockFatalError};
use instrumented_state::InstrumentedStateProvider; use instrumented_state::InstrumentedStateProvider;
use payload_processor::sparse_trie::StateRootComputeOutcome; use payload_processor::sparse_trie::StateRootComputeOutcome;
use persistence_state::CurrentPersistenceAction; use persistence_state::CurrentPersistenceAction;
use precompile_cache::{CachedPrecompile, PrecompileCache};
use reth_chain_state::{ use reth_chain_state::{
CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates,
MemoryOverlayStateProvider, NewCanonicalChain, MemoryOverlayStateProvider, NewCanonicalChain,
@@ -32,7 +34,7 @@ use reth_engine_primitives::{
}; };
use reth_errors::{ConsensusError, ProviderResult}; use reth_errors::{ConsensusError, ProviderResult};
use reth_ethereum_primitives::EthPrimitives; use reth_ethereum_primitives::EthPrimitives;
use reth_evm::ConfigureEvm; use reth_evm::{ConfigureEvm, Evm};
use reth_payload_builder::PayloadBuilderHandle; use reth_payload_builder::PayloadBuilderHandle;
use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes, PayloadTypes}; use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes, PayloadTypes};
use reth_primitives_traits::{ use reth_primitives_traits::{
@@ -74,6 +76,7 @@ mod invalid_headers;
mod metrics; mod metrics;
mod payload_processor; mod payload_processor;
mod persistence_state; mod persistence_state;
pub mod precompile_cache;
// TODO(alexey): compare trie updates in `insert_block_inner` // TODO(alexey): compare trie updates in `insert_block_inner`
#[expect(unused)] #[expect(unused)]
mod trie_updates; mod trie_updates;
@@ -613,6 +616,8 @@ where
payload_processor: PayloadProcessor<N, C>, payload_processor: PayloadProcessor<N, C>,
/// The EVM configuration. /// The EVM configuration.
evm_config: C, evm_config: C,
/// Precompile cache.
precompile_cache: PrecompileCache,
} }
impl<N, P: Debug, T: PayloadTypes + Debug, V: Debug, C: Debug> std::fmt::Debug impl<N, P: Debug, T: PayloadTypes + Debug, V: Debug, C: Debug> std::fmt::Debug
@@ -636,6 +641,8 @@ where
.field("metrics", &self.metrics) .field("metrics", &self.metrics)
.field("invalid_block_hook", &format!("{:p}", self.invalid_block_hook)) .field("invalid_block_hook", &format!("{:p}", self.invalid_block_hook))
.field("engine_kind", &self.engine_kind) .field("engine_kind", &self.engine_kind)
.field("payload_processor", &self.payload_processor)
.field("evm_config", &self.evm_config)
.finish() .finish()
} }
} }
@@ -675,8 +682,14 @@ where
) -> Self { ) -> Self {
let (incoming_tx, incoming) = std::sync::mpsc::channel(); let (incoming_tx, incoming) = std::sync::mpsc::channel();
let payload_processor = let precompile_cache = PrecompileCache::default();
PayloadProcessor::new(WorkloadExecutor::default(), evm_config.clone(), &config);
let payload_processor = PayloadProcessor::new(
WorkloadExecutor::default(),
evm_config.clone(),
&config,
precompile_cache.clone(),
);
Self { Self {
provider, provider,
@@ -697,6 +710,7 @@ where
engine_kind, engine_kind,
payload_processor, payload_processor,
evm_config, evm_config,
precompile_cache,
} }
} }
@@ -2619,7 +2633,14 @@ where
.with_bundle_update() .with_bundle_update()
.without_state_clear() .without_state_clear()
.build(); .build();
let executor = self.evm_config.executor_for_block(&mut db, block); let mut executor = self.evm_config.executor_for_block(&mut db, block);
if self.config.precompile_cache_enabled() {
executor.evm_mut().precompiles_mut().map_precompiles(|_, precompile| {
CachedPrecompile::wrap(precompile, self.precompile_cache.clone())
});
}
let execution_start = Instant::now(); let execution_start = Instant::now();
let output = self.metrics.executor.execute_metered( let output = self.metrics.executor.execute_metered(
executor, executor,

View File

@@ -6,6 +6,7 @@ use crate::tree::{
prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmTaskEvent}, prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmTaskEvent},
sparse_trie::StateRootComputeOutcome, sparse_trie::StateRootComputeOutcome,
}, },
precompile_cache::PrecompileCache,
sparse_trie::SparseTrieTask, sparse_trie::SparseTrieTask,
StateProviderBuilder, TreeConfig, StateProviderBuilder, TreeConfig,
}; };
@@ -58,12 +59,21 @@ pub struct PayloadProcessor<N, Evm> {
disable_transaction_prewarming: bool, disable_transaction_prewarming: bool,
/// Determines how to configure the evm for execution. /// Determines how to configure the evm for execution.
evm_config: Evm, evm_config: Evm,
/// whether precompile cache should be enabled.
precompile_cache_enabled: bool,
/// Precompile cache.
precompile_cache: PrecompileCache,
_marker: std::marker::PhantomData<N>, _marker: std::marker::PhantomData<N>,
} }
impl<N, Evm> PayloadProcessor<N, Evm> { impl<N, Evm> PayloadProcessor<N, Evm> {
/// Creates a new payload processor. /// Creates a new payload processor.
pub fn new(executor: WorkloadExecutor, evm_config: Evm, config: &TreeConfig) -> Self { pub fn new(
executor: WorkloadExecutor,
evm_config: Evm,
config: &TreeConfig,
precompile_cache: PrecompileCache,
) -> Self {
Self { Self {
executor, executor,
execution_cache: Default::default(), execution_cache: Default::default(),
@@ -71,6 +81,8 @@ impl<N, Evm> PayloadProcessor<N, Evm> {
cross_block_cache_size: config.cross_block_cache_size(), cross_block_cache_size: config.cross_block_cache_size(),
disable_transaction_prewarming: config.disable_caching_and_prewarming(), disable_transaction_prewarming: config.disable_caching_and_prewarming(),
evm_config, evm_config,
precompile_cache_enabled: config.precompile_cache_enabled(),
precompile_cache,
_marker: Default::default(), _marker: Default::default(),
} }
} }
@@ -253,6 +265,8 @@ where
provider: provider_builder, provider: provider_builder,
metrics: PrewarmMetrics::default(), metrics: PrewarmMetrics::default(),
terminate_execution: Arc::new(AtomicBool::new(false)), terminate_execution: Arc::new(AtomicBool::new(false)),
precompile_cache_enabled: self.precompile_cache_enabled,
precompile_cache: self.precompile_cache.clone(),
}; };
let prewarm_task = PrewarmCacheTask::new( let prewarm_task = PrewarmCacheTask::new(
@@ -426,6 +440,7 @@ mod tests {
payload_processor::{ payload_processor::{
evm_state_to_hashed_post_state, executor::WorkloadExecutor, PayloadProcessor, evm_state_to_hashed_post_state, executor::WorkloadExecutor, PayloadProcessor,
}, },
precompile_cache::PrecompileCache,
StateProviderBuilder, TreeConfig, StateProviderBuilder, TreeConfig,
}; };
use alloy_evm::block::StateChangeSource; use alloy_evm::block::StateChangeSource;
@@ -548,6 +563,7 @@ mod tests {
WorkloadExecutor::default(), WorkloadExecutor::default(),
EthEvmConfig::new(factory.chain_spec()), EthEvmConfig::new(factory.chain_spec()),
&TreeConfig::default(), &TreeConfig::default(),
PrecompileCache::default(),
); );
let provider = BlockchainProvider::new(factory).unwrap(); let provider = BlockchainProvider::new(factory).unwrap();
let mut handle = payload_processor.spawn( let mut handle = payload_processor.spawn(

View File

@@ -5,6 +5,7 @@ use crate::tree::{
payload_processor::{ payload_processor::{
executor::WorkloadExecutor, multiproof::MultiProofMessage, ExecutionCache, executor::WorkloadExecutor, multiproof::MultiProofMessage, ExecutionCache,
}, },
precompile_cache::{CachedPrecompile, PrecompileCache},
StateProviderBuilder, StateProviderBuilder,
}; };
use alloy_consensus::transaction::Recovered; use alloy_consensus::transaction::Recovered;
@@ -205,6 +206,8 @@ pub(super) struct PrewarmContext<N: NodePrimitives, P, Evm> {
pub(super) metrics: PrewarmMetrics, pub(super) metrics: PrewarmMetrics,
/// An atomic bool that tells prewarm tasks to not start any more execution. /// An atomic bool that tells prewarm tasks to not start any more execution.
pub(super) terminate_execution: Arc<AtomicBool>, pub(super) terminate_execution: Arc<AtomicBool>,
pub(super) precompile_cache_enabled: bool,
pub(super) precompile_cache: PrecompileCache,
} }
impl<N, P, Evm> PrewarmContext<N, P, Evm> impl<N, P, Evm> PrewarmContext<N, P, Evm>
@@ -226,6 +229,8 @@ where
provider, provider,
metrics, metrics,
terminate_execution, terminate_execution,
precompile_cache_enabled,
precompile_cache,
} = self; } = self;
let state_provider = match provider.build() { let state_provider = match provider.build() {
@@ -253,7 +258,13 @@ where
evm_env.cfg_env.disable_nonce_check = true; evm_env.cfg_env.disable_nonce_check = true;
// create a new executor and disable nonce checks in the env // create a new executor and disable nonce checks in the env
let evm = evm_config.evm_with_env(state_provider, evm_env); let mut evm = evm_config.evm_with_env(state_provider, evm_env);
if precompile_cache_enabled {
evm.precompiles_mut().map_precompiles(|_, precompile| {
CachedPrecompile::wrap(precompile, precompile_cache.clone())
});
}
Some((evm, evm_config, metrics, terminate_execution)) Some((evm, evm_config, metrics, terminate_execution))
} }

View File

@@ -0,0 +1,171 @@
//! Contains a precompile cache that is backed by a moka cache.
use reth_evm::precompiles::{DynPrecompile, Precompile};
use revm::precompile::{PrecompileOutput, PrecompileResult};
use revm_primitives::Bytes;
use std::sync::Arc;
/// Cache for precompiles, for each input stores the result.
#[derive(Debug, Clone)]
pub struct PrecompileCache(
Arc<mini_moka::sync::Cache<CacheKey, CacheEntry, alloy_primitives::map::DefaultHashBuilder>>,
);
impl Default for PrecompileCache {
fn default() -> Self {
Self(Arc::new(
mini_moka::sync::CacheBuilder::new(100_000)
.build_with_hasher(alloy_primitives::map::DefaultHashBuilder::default()),
))
}
}
impl PrecompileCache {
fn get(&self, key: &CacheKey) -> Option<CacheEntry> {
self.0.get(key)
}
fn insert(&self, key: CacheKey, value: CacheEntry) {
self.0.insert(key, value);
}
fn weighted_size(&self) -> u64 {
self.0.weighted_size()
}
}
/// Cache key, precompile call input.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey(alloy_primitives::Bytes);
/// Cache entry, precompile successful output.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CacheEntry(PrecompileOutput);
impl CacheEntry {
const fn gas_used(&self) -> u64 {
self.0.gas_used
}
fn to_precompile_result(&self) -> PrecompileResult {
Ok(self.0.clone())
}
}
/// A cache for precompile inputs / outputs.
#[derive(Debug)]
pub(crate) struct CachedPrecompile {
/// Cache for precompile results and gas bounds.
cache: PrecompileCache,
/// The precompile.
precompile: DynPrecompile,
/// Cache metrics.
metrics: CachedPrecompileMetrics,
}
impl CachedPrecompile {
/// `CachedPrecompile` constructor.
pub(crate) fn new(precompile: DynPrecompile, cache: PrecompileCache) -> Self {
Self { precompile, cache, metrics: Default::default() }
}
pub(crate) fn wrap(precompile: DynPrecompile, cache: PrecompileCache) -> DynPrecompile {
let wrapped = Self::new(precompile, cache);
move |data: &[u8], gas_limit: u64| -> PrecompileResult { wrapped.call(data, gas_limit) }
.into()
}
fn increment_by_one_precompile_cache_hits(&self) {
self.metrics.precompile_cache_hits.increment(1);
}
fn increment_by_one_precompile_cache_misses(&self) {
self.metrics.precompile_cache_misses.increment(1);
}
fn increment_by_one_precompile_errors(&self) {
self.metrics.precompile_errors.increment(1);
}
fn update_precompile_cache_size(&self) {
let new_size = self.cache.weighted_size();
self.metrics.precompile_cache_size.set(new_size as f64);
}
}
impl Precompile for CachedPrecompile {
fn call(&self, data: &[u8], gas_limit: u64) -> PrecompileResult {
let key = CacheKey(Bytes::copy_from_slice(data));
if let Some(entry) = &self.cache.get(&key) {
self.increment_by_one_precompile_cache_hits();
if gas_limit >= entry.gas_used() {
return entry.to_precompile_result()
}
}
let result = self.precompile.call(data, gas_limit);
match &result {
Ok(output) => {
self.increment_by_one_precompile_cache_misses();
self.cache.insert(key, CacheEntry(output.clone()));
}
_ => {
self.increment_by_one_precompile_errors();
}
}
self.update_precompile_cache_size();
result
}
}
/// Metrics for the cached precompile.
#[derive(reth_metrics::Metrics, Clone)]
#[metrics(scope = "sync.caching")]
pub(crate) struct CachedPrecompileMetrics {
/// Precompile cache hits
precompile_cache_hits: metrics::Counter,
/// Precompile cache misses
precompile_cache_misses: metrics::Counter,
/// Precompile cache size
///
/// NOTE: this uses the moka caches`weighted_size` method to calculate size.
precompile_cache_size: metrics::Gauge,
/// Precompile execution errors.
precompile_errors: metrics::Counter,
}
#[cfg(test)]
mod tests {
use super::*;
use revm::precompile::PrecompileOutput;
#[test]
fn test_precompile_cache_basic() {
let dyn_precompile: DynPrecompile = |_input: &[u8], _gas: u64| -> PrecompileResult {
Ok(PrecompileOutput { gas_used: 0, bytes: Bytes::default() })
}
.into();
let cache = CachedPrecompile::new(dyn_precompile, PrecompileCache::default());
let key = CacheKey(b"test_input".into());
let output = PrecompileOutput {
gas_used: 50,
bytes: alloy_primitives::Bytes::copy_from_slice(b"cached_result"),
};
let expected = CacheEntry(output);
cache.cache.insert(key.clone(), expected.clone());
let actual = cache.cache.get(&key).unwrap();
assert_eq!(actual, expected);
}
}

View File

@@ -28,7 +28,10 @@ use alloy_primitives::{Bytes, U256};
use core::{convert::Infallible, fmt::Debug}; use core::{convert::Infallible, fmt::Debug};
use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET}; use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned}; use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
use reth_evm::{ConfigureEvm, EvmEnv, EvmFactory, NextBlockEnvAttributes, TransactionEnv}; use reth_evm::{
precompiles::PrecompilesMap, ConfigureEvm, EvmEnv, EvmFactory, NextBlockEnvAttributes,
TransactionEnv,
};
use reth_primitives_traits::{SealedBlock, SealedHeader}; use reth_primitives_traits::{SealedBlock, SealedHeader};
use revm::{ use revm::{
context::{BlockEnv, CfgEnv}, context::{BlockEnv, CfgEnv},
@@ -123,6 +126,7 @@ where
+ FromRecoveredTx<TransactionSigned> + FromRecoveredTx<TransactionSigned>
+ FromTxWithEncoded<TransactionSigned>, + FromTxWithEncoded<TransactionSigned>,
Spec = SpecId, Spec = SpecId,
Precompiles = PrecompilesMap,
> + Clone > + Clone
+ Debug + Debug
+ Send + Send

View File

@@ -2,6 +2,7 @@ use crate::EthEvmConfig;
use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::Header; use alloy_consensus::Header;
use alloy_eips::eip7685::Requests; use alloy_eips::eip7685::Requests;
use alloy_evm::precompiles::PrecompilesMap;
use parking_lot::Mutex; use parking_lot::Mutex;
use reth_ethereum_primitives::{Receipt, TransactionSigned}; use reth_ethereum_primitives::{Receipt, TransactionSigned};
use reth_evm::{ use reth_evm::{
@@ -54,7 +55,7 @@ impl BlockExecutorFactory for MockEvmConfig {
fn create_executor<'a, DB, I>( fn create_executor<'a, DB, I>(
&'a self, &'a self,
evm: EthEvm<&'a mut State<DB>, I>, evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
_ctx: Self::ExecutionCtx<'a>, _ctx: Self::ExecutionCtx<'a>,
) -> impl BlockExecutorFor<'a, Self, DB, I> ) -> impl BlockExecutorFor<'a, Self, DB, I>
where where
@@ -69,7 +70,7 @@ impl BlockExecutorFactory for MockEvmConfig {
#[derive(derive_more::Debug)] #[derive(derive_more::Debug)]
pub struct MockExecutor<'a, DB: Database, I> { pub struct MockExecutor<'a, DB: Database, I> {
result: ExecutionOutcome, result: ExecutionOutcome,
evm: EthEvm<&'a mut State<DB>, I>, evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
#[debug(skip)] #[debug(skip)]
hook: Option<Box<dyn reth_evm::OnStateHook>>, hook: Option<Box<dyn reth_evm::OnStateHook>>,
} }
@@ -77,7 +78,7 @@ pub struct MockExecutor<'a, DB: Database, I> {
impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExecutor impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExecutor
for MockExecutor<'a, DB, I> for MockExecutor<'a, DB, I>
{ {
type Evm = EthEvm<&'a mut State<DB>, I>; type Evm = EthEvm<&'a mut State<DB>, I, PrecompilesMap>;
type Transaction = TransactionSigned; type Transaction = TransactionSigned;
type Receipt = Receipt; type Receipt = Receipt;

View File

@@ -21,7 +21,6 @@ reth-storage-errors.workspace = true
reth-trie-common.workspace = true reth-trie-common.workspace = true
revm.workspace = true revm.workspace = true
op-revm = { workspace = true, optional = true }
# alloy # alloy
alloy-primitives.workspace = true alloy-primitives.workspace = true
@@ -50,7 +49,6 @@ std = [
"revm/std", "revm/std",
"reth-ethereum-forks/std", "reth-ethereum-forks/std",
"alloy-evm/std", "alloy-evm/std",
"op-revm?/std",
"reth-execution-errors/std", "reth-execution-errors/std",
"reth-execution-types/std", "reth-execution-types/std",
"reth-storage-errors/std", "reth-storage-errors/std",
@@ -66,4 +64,4 @@ test-utils = [
"reth-trie-common/test-utils", "reth-trie-common/test-utils",
"reth-ethereum-primitives/test-utils", "reth-ethereum-primitives/test-utils",
] ]
op = ["op-revm", "alloy-evm/op", "reth-primitives-traits/op"] op = ["alloy-evm/op", "reth-primitives-traits/op"]

View File

@@ -19,8 +19,15 @@ extern crate alloc;
use crate::execute::BasicBlockBuilder; use crate::execute::BasicBlockBuilder;
use alloc::vec::Vec; use alloc::vec::Vec;
use alloy_eips::{eip2930::AccessList, eip4895::Withdrawals}; use alloy_eips::{
use alloy_evm::block::{BlockExecutorFactory, BlockExecutorFor}; eip2718::{EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID},
eip2930::AccessList,
eip4895::Withdrawals,
};
use alloy_evm::{
block::{BlockExecutorFactory, BlockExecutorFor},
precompiles::PrecompilesMap,
};
use alloy_primitives::{Address, B256}; use alloy_primitives::{Address, B256};
use core::{error::Error, fmt::Debug}; use core::{error::Error, fmt::Debug};
use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder}; use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder};
@@ -109,6 +116,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
Tx: TransactionEnv Tx: TransactionEnv
+ FromRecoveredTx<TxTy<Self::Primitives>> + FromRecoveredTx<TxTy<Self::Primitives>>
+ FromTxWithEncoded<TxTy<Self::Primitives>>, + FromTxWithEncoded<TxTy<Self::Primitives>>,
Precompiles = PrecompilesMap,
>, >,
>; >;
@@ -354,6 +362,12 @@ impl TransactionEnv for TxEnv {
fn set_access_list(&mut self, access_list: AccessList) { fn set_access_list(&mut self, access_list: AccessList) {
self.access_list = access_list; self.access_list = access_list;
if self.tx_type == LEGACY_TX_TYPE_ID {
// if this was previously marked as legacy tx, this must be upgraded to eip2930 with an
// accesslist
self.tx_type = EIP2930_TX_TYPE_ID;
}
} }
} }

View File

@@ -59,6 +59,10 @@ pub struct EngineArgs {
/// Configure the number of reserved CPU cores for non-reth processes /// Configure the number of reserved CPU cores for non-reth processes
#[arg(long = "engine.reserved-cpu-cores", default_value_t = DEFAULT_RESERVED_CPU_CORES)] #[arg(long = "engine.reserved-cpu-cores", default_value_t = DEFAULT_RESERVED_CPU_CORES)]
pub reserved_cpu_cores: usize, pub reserved_cpu_cores: usize,
/// Enable precompile cache
#[arg(long = "engine.precompile-cache", default_value = "false")]
pub precompile_cache_enabled: bool,
} }
impl Default for EngineArgs { impl Default for EngineArgs {
@@ -75,6 +79,7 @@ impl Default for EngineArgs {
accept_execution_requests_hash: false, accept_execution_requests_hash: false,
max_proof_task_concurrency: DEFAULT_MAX_PROOF_TASK_CONCURRENCY, max_proof_task_concurrency: DEFAULT_MAX_PROOF_TASK_CONCURRENCY,
reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES, reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
precompile_cache_enabled: false,
} }
} }
} }
@@ -92,6 +97,7 @@ impl EngineArgs {
.with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024) .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
.with_max_proof_task_concurrency(self.max_proof_task_concurrency) .with_max_proof_task_concurrency(self.max_proof_task_concurrency)
.with_reserved_cpu_cores(self.reserved_cpu_cores) .with_reserved_cpu_cores(self.reserved_cpu_cores)
.with_precompile_cache_enabled(self.precompile_cache_enabled)
} }
} }

View File

@@ -1,6 +1,6 @@
use super::OpNodeCore; use super::OpNodeCore;
use crate::{OpEthApi, OpEthApiError}; use crate::{OpEthApi, OpEthApiError};
use alloy_consensus::TxType; use alloy_consensus::{transaction::Either, TxType};
use alloy_primitives::{Bytes, TxKind, U256}; use alloy_primitives::{Bytes, TxKind, U256};
use alloy_rpc_types_eth::transaction::TransactionRequest; use alloy_rpc_types_eth::transaction::TransactionRequest;
use op_revm::OpTransaction; use op_revm::OpTransaction;
@@ -149,7 +149,11 @@ where
.map(|v| v.saturating_to()) .map(|v| v.saturating_to())
.unwrap_or_default(), .unwrap_or_default(),
// EIP-7702 fields // EIP-7702 fields
authorization_list: authorization_list.unwrap_or_default(), authorization_list: authorization_list
.unwrap_or_default()
.into_iter()
.map(Either::Left)
.collect(),
}; };
Ok(OpTransaction { base, enveloped_tx: Some(Bytes::new()), deposit: Default::default() }) Ok(OpTransaction { base, enveloped_tx: Some(Bytes::new()), deposit: Default::default() })

View File

@@ -296,12 +296,12 @@ mod tests {
let mut buf = vec![]; let mut buf = vec![];
let bytecode = Bytecode::new_raw(Bytes::default()); let bytecode = Bytecode::new_raw(Bytes::default());
let len = bytecode.to_compact(&mut buf); let len = bytecode.to_compact(&mut buf);
assert_eq!(len, 51); assert_eq!(len, 14);
let mut buf = vec![]; let mut buf = vec![];
let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff"))); let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff")));
let len = bytecode.to_compact(&mut buf); let len = bytecode.to_compact(&mut buf);
assert_eq!(len, 53); assert_eq!(len, 17);
let mut buf = vec![]; let mut buf = vec![];
let bytecode = Bytecode(RevmBytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new( let bytecode = Bytecode(RevmBytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new(

View File

@@ -580,7 +580,11 @@ impl From<InvalidTransaction> for RpcInvalidTransactionError {
InvalidTransaction::Eip2930NotSupported | InvalidTransaction::Eip2930NotSupported |
InvalidTransaction::Eip1559NotSupported | InvalidTransaction::Eip1559NotSupported |
InvalidTransaction::Eip4844NotSupported | InvalidTransaction::Eip4844NotSupported |
InvalidTransaction::Eip7702NotSupported => Self::TxTypeNotSupported, InvalidTransaction::Eip7702NotSupported |
InvalidTransaction::Eip7873NotSupported => Self::TxTypeNotSupported,
InvalidTransaction::Eip7873MissingTarget => {
Self::other(internal_rpc_err(err.to_string()))
}
} }
} }
} }

View File

@@ -5,6 +5,7 @@ use alloy_consensus::TxType;
use alloy_evm::block::BlockExecutorFactory; use alloy_evm::block::BlockExecutorFactory;
use alloy_primitives::{TxKind, U256}; use alloy_primitives::{TxKind, U256};
use alloy_rpc_types::TransactionRequest; use alloy_rpc_types::TransactionRequest;
use alloy_signer::Either;
use reth_evm::{ConfigureEvm, EvmEnv, EvmFactory, SpecFor}; use reth_evm::{ConfigureEvm, EvmEnv, EvmFactory, SpecFor};
use reth_node_api::NodePrimitives; use reth_node_api::NodePrimitives;
use reth_rpc_eth_api::{ use reth_rpc_eth_api::{
@@ -139,7 +140,11 @@ where
.map(|v| v.saturating_to()) .map(|v| v.saturating_to())
.unwrap_or_default(), .unwrap_or_default(),
// EIP-7702 fields // EIP-7702 fields
authorization_list: authorization_list.unwrap_or_default(), authorization_list: authorization_list
.unwrap_or_default()
.into_iter()
.map(Either::Left)
.collect(),
}; };
Ok(env) Ok(env)

View File

@@ -53,6 +53,7 @@ allow = [
# https://github.com/rustls/webpki/blob/main/LICENSE ISC Style # https://github.com/rustls/webpki/blob/main/LICENSE ISC Style
"LicenseRef-rustls-webpki", "LicenseRef-rustls-webpki",
"CDLA-Permissive-2.0", "CDLA-Permissive-2.0",
"MPL-2.0",
] ]
# Allow 1 or more licenses on a per-crate basis, so that particular licenses # Allow 1 or more licenses on a per-crate basis, so that particular licenses

View File

@@ -7,6 +7,7 @@ use alloy_eips::eip4895::Withdrawal;
use alloy_evm::{ use alloy_evm::{
block::{BlockExecutorFactory, BlockExecutorFor, ExecutableTx}, block::{BlockExecutorFactory, BlockExecutorFor, ExecutableTx},
eth::{EthBlockExecutionCtx, EthBlockExecutor}, eth::{EthBlockExecutionCtx, EthBlockExecutor},
precompiles::PrecompilesMap,
EthEvm, EthEvmFactory, EthEvm, EthEvmFactory,
}; };
use alloy_sol_macro::sol; use alloy_sol_macro::sol;
@@ -101,7 +102,7 @@ impl BlockExecutorFactory for CustomEvmConfig {
fn create_executor<'a, DB, I>( fn create_executor<'a, DB, I>(
&'a self, &'a self,
evm: EthEvm<&'a mut State<DB>, I>, evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
ctx: EthBlockExecutionCtx<'a>, ctx: EthBlockExecutionCtx<'a>,
) -> impl BlockExecutorFor<'a, Self, DB, I> ) -> impl BlockExecutorFor<'a, Self, DB, I>
where where

View File

@@ -2,9 +2,9 @@
#![warn(unused_crate_dependencies)] #![warn(unused_crate_dependencies)]
use alloy_evm::{eth::EthEvmContext, EvmFactory}; use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, EvmFactory};
use alloy_genesis::Genesis; use alloy_genesis::Genesis;
use alloy_primitives::{address, Address, Bytes}; use alloy_primitives::{address, Bytes};
use reth::{ use reth::{
builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder},
tasks::TaskManager, tasks::TaskManager,
@@ -14,14 +14,11 @@ use reth_ethereum::{
evm::{ evm::{
primitives::{Database, EvmEnv}, primitives::{Database, EvmEnv},
revm::{ revm::{
context::{Cfg, Context, TxEnv}, context::{Context, TxEnv},
context_interface::{ context_interface::result::{EVMError, HaltReason},
result::{EVMError, HaltReason}, handler::EthPrecompiles,
ContextTr,
},
handler::{EthPrecompiles, PrecompileProvider},
inspector::{Inspector, NoOpInspector}, inspector::{Inspector, NoOpInspector},
interpreter::{interpreter::EthInterpreter, InputsImpl, InterpreterResult}, interpreter::interpreter::EthInterpreter,
precompile::{PrecompileFn, PrecompileOutput, PrecompileResult, Precompiles}, precompile::{PrecompileFn, PrecompileOutput, PrecompileResult, Precompiles},
primitives::hardfork::SpecId, primitives::hardfork::SpecId,
MainBuilder, MainContext, MainBuilder, MainContext,
@@ -46,20 +43,26 @@ pub struct MyEvmFactory;
impl EvmFactory for MyEvmFactory { impl EvmFactory for MyEvmFactory {
type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> = type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> =
EthEvm<DB, I, CustomPrecompiles>; EthEvm<DB, I, Self::Precompiles>;
type Tx = TxEnv; type Tx = TxEnv;
type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>; type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
type HaltReason = HaltReason; type HaltReason = HaltReason;
type Context<DB: Database> = EthEvmContext<DB>; type Context<DB: Database> = EthEvmContext<DB>;
type Spec = SpecId; type Spec = SpecId;
type Precompiles = PrecompilesMap;
fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> { fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
let evm = Context::mainnet() let spec = input.cfg_env.spec;
let mut evm = Context::mainnet()
.with_db(db) .with_db(db)
.with_cfg(input.cfg_env) .with_cfg(input.cfg_env)
.with_block(input.block_env) .with_block(input.block_env)
.build_mainnet_with_inspector(NoOpInspector {}) .build_mainnet_with_inspector(NoOpInspector {})
.with_precompiles(CustomPrecompiles::new()); .with_precompiles(PrecompilesMap::from_static(EthPrecompiles::default().precompiles));
if spec == SpecId::PRAGUE {
evm = evm.with_precompiles(PrecompilesMap::from_static(prague_custom()));
}
EthEvm::new(evm, false) EthEvm::new(evm, false)
} }
@@ -92,20 +95,6 @@ where
} }
} }
/// A custom precompile that contains static precompiles.
#[derive(Clone)]
pub struct CustomPrecompiles {
pub precompiles: EthPrecompiles,
}
impl CustomPrecompiles {
/// Given a [`PrecompileProvider`] and cache for a specific precompiles, create a
/// wrapper that can be used inside Evm.
fn new() -> Self {
Self { precompiles: EthPrecompiles::default() }
}
}
/// Returns precompiles for Fjor spec. /// Returns precompiles for Fjor spec.
pub fn prague_custom() -> &'static Precompiles { pub fn prague_custom() -> &'static Precompiles {
static INSTANCE: OnceLock<Precompiles> = OnceLock::new(); static INSTANCE: OnceLock<Precompiles> = OnceLock::new();
@@ -123,39 +112,6 @@ pub fn prague_custom() -> &'static Precompiles {
}) })
} }
impl<CTX: ContextTr> PrecompileProvider<CTX> for CustomPrecompiles {
type Output = InterpreterResult;
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
let spec_id = spec.clone().into();
if spec_id == SpecId::PRAGUE {
self.precompiles = EthPrecompiles { precompiles: prague_custom(), spec: spec.into() }
} else {
PrecompileProvider::<CTX>::set_spec(&mut self.precompiles, spec);
}
true
}
fn run(
&mut self,
context: &mut CTX,
address: &Address,
inputs: &InputsImpl,
is_static: bool,
gas_limit: u64,
) -> Result<Option<Self::Output>, String> {
self.precompiles.run(context, address, inputs, is_static, gas_limit)
}
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
self.precompiles.warm_addresses()
}
fn contains(&self, address: &Address) -> bool {
self.precompiles.contains(address)
}
}
#[tokio::main] #[tokio::main]
async fn main() -> eyre::Result<()> { async fn main() -> eyre::Result<()> {
let _guard = RethTracer::new().init()?; let _guard = RethTracer::new().init()?;

View File

@@ -5,6 +5,7 @@ use alloy_evm::{
BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
BlockExecutorFor, ExecutableTx, OnStateHook, BlockExecutorFor, ExecutableTx, OnStateHook,
}, },
precompiles::PrecompilesMap,
Database, Evm, EvmEnv, Database, Evm, EvmEnv,
}; };
use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor, OpEvm}; use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor, OpEvm};
@@ -116,7 +117,7 @@ impl BlockExecutorFactory for CustomEvmConfig {
fn create_executor<'a, DB, I>( fn create_executor<'a, DB, I>(
&'a self, &'a self,
evm: OpEvm<&'a mut State<DB>, I>, evm: OpEvm<&'a mut State<DB>, I, PrecompilesMap>,
ctx: OpBlockExecutionCtx, ctx: OpBlockExecutionCtx,
) -> impl BlockExecutorFor<'a, Self, DB, I> ) -> impl BlockExecutorFor<'a, Self, DB, I>
where where

View File

@@ -2,12 +2,17 @@
#![warn(unused_crate_dependencies)] #![warn(unused_crate_dependencies)]
use alloy_evm::{eth::EthEvmContext, EvmFactory}; use alloy_evm::{
eth::EthEvmContext,
precompiles::{DynPrecompile, Precompile, PrecompilesMap},
Evm, EvmFactory,
};
use alloy_genesis::Genesis; use alloy_genesis::Genesis;
use alloy_primitives::{Address, Bytes}; use alloy_primitives::Bytes;
use parking_lot::RwLock; use parking_lot::RwLock;
use reth::{ use reth::{
builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder},
revm::precompile::PrecompileResult,
tasks::TaskManager, tasks::TaskManager,
}; };
use reth_ethereum::{ use reth_ethereum::{
@@ -15,14 +20,11 @@ use reth_ethereum::{
evm::{ evm::{
primitives::{Database, EvmEnv}, primitives::{Database, EvmEnv},
revm::{ revm::{
context::{Cfg, Context, TxEnv}, context::{Context, TxEnv},
context_interface::{ context_interface::result::{EVMError, HaltReason},
result::{EVMError, HaltReason}, handler::EthPrecompiles,
ContextTr,
},
handler::{EthPrecompiles, PrecompileProvider},
inspector::{Inspector, NoOpInspector}, inspector::{Inspector, NoOpInspector},
interpreter::{interpreter::EthInterpreter, InputsImpl, InterpreterResult}, interpreter::interpreter::EthInterpreter,
primitives::hardfork::SpecId, primitives::hardfork::SpecId,
MainBuilder, MainContext, MainBuilder, MainContext,
}, },
@@ -38,12 +40,10 @@ use reth_ethereum::{
}; };
use reth_tracing::{RethTracer, Tracer}; use reth_tracing::{RethTracer, Tracer};
use schnellru::{ByLength, LruMap}; use schnellru::{ByLength, LruMap};
use std::{collections::HashMap, sync::Arc}; use std::sync::Arc;
/// Type alias for the LRU cache used within the [`PrecompileCache`]. /// Type alias for the LRU cache used within the [`PrecompileCache`].
type PrecompileLRUCache = LruMap<(SpecId, Bytes, u64), Result<InterpreterResult, String>>; type PrecompileLRUCache = LruMap<(Bytes, u64), PrecompileResult>;
type WrappedEthEvm<DB, I> = EthEvm<DB, I, WrappedPrecompile<EthPrecompiles>>;
/// A cache for precompile inputs / outputs. /// A cache for precompile inputs / outputs.
/// ///
@@ -52,26 +52,28 @@ type WrappedEthEvm<DB, I> = EthEvm<DB, I, WrappedPrecompile<EthPrecompiles>>;
/// ///
/// NOTE: This does not work with "context stateful precompiles", ie `ContextStatefulPrecompile` or /// NOTE: This does not work with "context stateful precompiles", ie `ContextStatefulPrecompile` or
/// `ContextStatefulPrecompileMut`. They are explicitly banned. /// `ContextStatefulPrecompileMut`. They are explicitly banned.
#[derive(Debug, Default)] #[derive(Debug)]
pub struct PrecompileCache { pub struct PrecompileCache {
/// Caches for each precompile input / output. /// Caches for each precompile input / output.
cache: HashMap<Address, PrecompileLRUCache>, cache: PrecompileLRUCache,
} }
/// Custom EVM factory. /// Custom EVM factory.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
#[non_exhaustive] #[non_exhaustive]
pub struct MyEvmFactory { pub struct MyEvmFactory {
precompile_cache: Arc<RwLock<PrecompileCache>>, precompile_cache: Arc<RwLock<PrecompileCache>>,
} }
impl EvmFactory for MyEvmFactory { impl EvmFactory for MyEvmFactory {
type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> = WrappedEthEvm<DB, I>; type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> =
EthEvm<DB, I, PrecompilesMap>;
type Tx = TxEnv; type Tx = TxEnv;
type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>; type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
type HaltReason = HaltReason; type HaltReason = HaltReason;
type Context<DB: Database> = EthEvmContext<DB>; type Context<DB: Database> = EthEvmContext<DB>;
type Spec = SpecId; type Spec = SpecId;
type Precompiles = PrecompilesMap;
fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> { fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
let new_cache = self.precompile_cache.clone(); let new_cache = self.precompile_cache.clone();
@@ -81,9 +83,15 @@ impl EvmFactory for MyEvmFactory {
.with_cfg(input.cfg_env) .with_cfg(input.cfg_env)
.with_block(input.block_env) .with_block(input.block_env)
.build_mainnet_with_inspector(NoOpInspector {}) .build_mainnet_with_inspector(NoOpInspector {})
.with_precompiles(WrappedPrecompile::new(EthPrecompiles::default(), new_cache)); .with_precompiles(PrecompilesMap::from_static(EthPrecompiles::default().precompiles));
EthEvm::new(evm, false) let mut evm = EthEvm::new(evm, false);
evm.precompiles_mut().map_precompiles(|_, precompile| {
WrappedPrecompile::wrap(precompile, new_cache.clone())
});
evm
} }
fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>, EthInterpreter>>( fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>, EthInterpreter>>(
@@ -98,84 +106,64 @@ impl EvmFactory for MyEvmFactory {
/// A custom precompile that contains the cache and precompile it wraps. /// A custom precompile that contains the cache and precompile it wraps.
#[derive(Clone)] #[derive(Clone)]
pub struct WrappedPrecompile<P> { pub struct WrappedPrecompile {
/// The precompile to wrap. /// The precompile to wrap.
precompile: P, precompile: DynPrecompile,
/// The cache to use. /// The cache to use.
cache: Arc<RwLock<PrecompileCache>>, cache: Arc<RwLock<PrecompileCache>>,
/// The spec id to use.
spec: SpecId,
} }
impl<P> WrappedPrecompile<P> { impl WrappedPrecompile {
/// Given a [`PrecompileProvider`] and cache for a specific precompiles, create a fn new(precompile: DynPrecompile, cache: Arc<RwLock<PrecompileCache>>) -> Self {
Self { precompile, cache }
}
/// Given a [`DynPrecompile`] and cache for a specific precompiles, create a
/// wrapper that can be used inside Evm. /// wrapper that can be used inside Evm.
fn new(precompile: P, cache: Arc<RwLock<PrecompileCache>>) -> Self { fn wrap(precompile: DynPrecompile, cache: Arc<RwLock<PrecompileCache>>) -> DynPrecompile {
WrappedPrecompile { precompile, cache: cache.clone(), spec: SpecId::default() } let wrapped = Self::new(precompile, cache);
move |data: &[u8], gas_limit: u64| -> PrecompileResult { wrapped.call(data, gas_limit) }
.into()
} }
} }
impl<CTX: ContextTr, P: PrecompileProvider<CTX, Output = InterpreterResult>> PrecompileProvider<CTX> impl Precompile for WrappedPrecompile {
for WrappedPrecompile<P> fn call(&self, data: &[u8], gas: u64) -> PrecompileResult {
{
type Output = P::Output;
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
self.precompile.set_spec(spec.clone());
self.spec = spec.into();
true
}
fn run(
&mut self,
context: &mut CTX,
address: &Address,
inputs: &InputsImpl,
is_static: bool,
gas_limit: u64,
) -> Result<Option<Self::Output>, String> {
let mut cache = self.cache.write(); let mut cache = self.cache.write();
let key = (self.spec, inputs.input.clone(), gas_limit); let key = (Bytes::copy_from_slice(data), gas);
// get the result if it exists // get the result if it exists
if let Some(precompiles) = cache.cache.get_mut(address) { if let Some(result) = cache.cache.get(&key) {
if let Some(result) = precompiles.get(&key) { return result.clone()
return result.clone().map(Some)
}
} }
// call the precompile if cache miss // call the precompile if cache miss
let output = self.precompile.run(context, address, inputs, is_static, gas_limit); let output = self.precompile.call(data, gas);
if let Some(output) = output.clone().transpose() { // insert the result into the cache
// insert the result into the cache cache.cache.insert(key, output.clone());
cache
.cache
.entry(*address)
.or_insert(PrecompileLRUCache::new(ByLength::new(1024)))
.insert(key, output);
}
output output
} }
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
self.precompile.warm_addresses()
}
fn contains(&self, address: &Address) -> bool {
self.precompile.contains(address)
}
} }
/// Builds a regular ethereum block executor that uses the custom EVM. /// Builds a regular ethereum block executor that uses the custom EVM.
#[derive(Debug, Default, Clone)] #[derive(Debug, Clone)]
#[non_exhaustive] #[non_exhaustive]
pub struct MyExecutorBuilder { pub struct MyExecutorBuilder {
/// The precompile cache to use for all executors. /// The precompile cache to use for all executors.
precompile_cache: Arc<RwLock<PrecompileCache>>, precompile_cache: Arc<RwLock<PrecompileCache>>,
} }
impl Default for MyExecutorBuilder {
fn default() -> Self {
let precompile_cache = PrecompileCache {
cache: LruMap::<(Bytes, u64), PrecompileResult>::new(ByLength::new(100)),
};
Self { precompile_cache: Arc::new(RwLock::new(precompile_cache)) }
}
}
impl<Node> ExecutorBuilder<Node> for MyExecutorBuilder impl<Node> ExecutorBuilder<Node> for MyExecutorBuilder
where where
Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>, Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,