mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: alloy-evm and new revm integration (#14021)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> Co-authored-by: rakita <rakita@users.noreply.github.com>
This commit is contained in:
@ -12,8 +12,9 @@ use reth::{
|
||||
builder::{components::ExecutorBuilder, BuilderContext, FullNodeTypes},
|
||||
cli::Cli,
|
||||
revm::{
|
||||
db::State,
|
||||
primitives::{address, Address},
|
||||
DatabaseCommit, State,
|
||||
DatabaseCommit,
|
||||
},
|
||||
};
|
||||
use reth_chainspec::{ChainSpec, EthereumHardforks};
|
||||
@ -204,7 +205,7 @@ pub fn apply_withdrawals_contract_call(
|
||||
|
||||
// Clean-up post system tx context
|
||||
state.remove(&SYSTEM_ADDRESS);
|
||||
state.remove(&evm.block().coinbase);
|
||||
state.remove(&evm.block().beneficiary);
|
||||
|
||||
evm.db_mut().commit(state);
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ reth-ethereum-payload-builder.workspace = true
|
||||
reth-node-ethereum = { workspace = true, features = ["test-utils"] }
|
||||
reth-tracing.workspace = true
|
||||
reth-evm.workspace = true
|
||||
alloy-evm.workspace = true
|
||||
alloy-genesis.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use alloy_consensus::Header;
|
||||
use alloy_evm::{eth::EthEvmContext, EvmFactory};
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::{address, Address, Bytes};
|
||||
use reth::{
|
||||
@ -12,21 +13,24 @@ use reth::{
|
||||
},
|
||||
payload::{EthBuiltPayload, EthPayloadBuilderAttributes},
|
||||
revm::{
|
||||
handler::register::EvmHandler,
|
||||
inspector_handle_register,
|
||||
precompile::{Precompile, PrecompileOutput, PrecompileSpecId},
|
||||
primitives::{
|
||||
CfgEnvWithHandlerCfg, EVMError, Env, HaltReason, HandlerCfg, PrecompileResult, SpecId,
|
||||
TxEnv,
|
||||
context::{Cfg, Context, TxEnv},
|
||||
context_interface::{
|
||||
result::{EVMError, HaltReason},
|
||||
ContextTr,
|
||||
},
|
||||
ContextPrecompiles, EvmBuilder, GetInspector,
|
||||
handler::{EthPrecompiles, PrecompileProvider},
|
||||
inspector::{Inspector, NoOpInspector},
|
||||
interpreter::{interpreter::EthInterpreter, InterpreterResult},
|
||||
precompile::{PrecompileFn, PrecompileOutput, PrecompileResult, Precompiles},
|
||||
specification::hardfork::SpecId,
|
||||
MainBuilder, MainContext,
|
||||
},
|
||||
rpc::types::engine::PayloadAttributes,
|
||||
tasks::TaskManager,
|
||||
transaction_pool::{PoolTransaction, TransactionPool},
|
||||
};
|
||||
use reth_chainspec::{Chain, ChainSpec};
|
||||
use reth_evm::{env::EvmEnv, Database};
|
||||
use reth_evm::{Database, EvmEnv};
|
||||
use reth_evm_ethereum::{EthEvm, EthEvmConfig};
|
||||
use reth_node_api::{
|
||||
ConfigureEvm, ConfigureEvmEnv, FullNodeTypes, NextBlockEnvAttributes, NodeTypes,
|
||||
@ -39,7 +43,44 @@ use reth_node_ethereum::{
|
||||
};
|
||||
use reth_primitives::{EthPrimitives, TransactionSigned};
|
||||
use reth_tracing::{RethTracer, Tracer};
|
||||
use std::{convert::Infallible, sync::Arc};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
/// Custom EVM configuration.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct MyEvmFactory;
|
||||
|
||||
impl EvmFactory<EvmEnv> for MyEvmFactory {
|
||||
type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> =
|
||||
EthEvm<DB, I, CustomPrecompiles<EthEvmContext<DB>>>;
|
||||
type Tx = TxEnv;
|
||||
type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
|
||||
type HaltReason = HaltReason;
|
||||
type Context<DB: Database> = EthEvmContext<DB>;
|
||||
|
||||
fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
|
||||
let evm = Context::mainnet()
|
||||
.with_db(db)
|
||||
.with_cfg(input.cfg_env)
|
||||
.with_block(input.block_env)
|
||||
.build_mainnet_with_inspector(NoOpInspector {})
|
||||
.with_precompiles(CustomPrecompiles::new());
|
||||
|
||||
EthEvm::new(evm, false)
|
||||
}
|
||||
|
||||
fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>, EthInterpreter>>(
|
||||
&self,
|
||||
db: DB,
|
||||
input: EvmEnv,
|
||||
inspector: I,
|
||||
) -> Self::Evm<DB, I> {
|
||||
EthEvm::new(self.create_evm(db, input).into_inner().with_inspector(inspector), true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom EVM configuration
|
||||
#[derive(Debug, Clone)]
|
||||
@ -47,42 +88,13 @@ use std::{convert::Infallible, sync::Arc};
|
||||
pub struct MyEvmConfig {
|
||||
/// Wrapper around mainnet configuration
|
||||
inner: EthEvmConfig,
|
||||
/// Custom EVM factory.
|
||||
evm_factory: MyEvmFactory,
|
||||
}
|
||||
|
||||
impl MyEvmConfig {
|
||||
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
Self { inner: EthEvmConfig::new(chain_spec) }
|
||||
}
|
||||
}
|
||||
|
||||
impl MyEvmConfig {
|
||||
/// Sets the precompiles to the EVM handler
|
||||
///
|
||||
/// This will be invoked when the EVM is created via [ConfigureEvm::evm_with_env] or
|
||||
/// [ConfigureEvm::evm_with_env_and_inspector]
|
||||
///
|
||||
/// This will use the default mainnet precompiles and add additional precompiles.
|
||||
pub fn set_precompiles<EXT, DB>(handler: &mut EvmHandler<EXT, DB>)
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
// first we need the evm spec id, which determines the precompiles
|
||||
let spec_id = handler.cfg.spec_id;
|
||||
|
||||
// install the precompiles
|
||||
handler.pre_execution.load_precompiles = Arc::new(move || {
|
||||
let mut precompiles = ContextPrecompiles::new(PrecompileSpecId::from_spec_id(spec_id));
|
||||
precompiles.extend([(
|
||||
address!("0000000000000000000000000000000000000999"),
|
||||
Precompile::Env(Self::my_precompile).into(),
|
||||
)]);
|
||||
precompiles
|
||||
});
|
||||
}
|
||||
|
||||
/// A custom precompile that does nothing
|
||||
fn my_precompile(_data: &Bytes, _gas: u64, _env: &Env) -> PrecompileResult {
|
||||
Ok(PrecompileOutput::new(0, Bytes::new()))
|
||||
pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
Self { inner: EthEvmConfig::new(chain_spec), evm_factory: MyEvmFactory::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,50 +123,10 @@ impl ConfigureEvmEnv for MyEvmConfig {
|
||||
}
|
||||
|
||||
impl ConfigureEvm for MyEvmConfig {
|
||||
type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>;
|
||||
type EvmError<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
|
||||
type HaltReason = HaltReason;
|
||||
type EvmFactory = MyEvmFactory;
|
||||
|
||||
fn evm_with_env<DB: Database>(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> {
|
||||
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg {
|
||||
cfg_env: evm_env.cfg_env,
|
||||
handler_cfg: HandlerCfg::new(evm_env.spec),
|
||||
};
|
||||
EvmBuilder::default()
|
||||
.with_db(db)
|
||||
.with_cfg_env_with_handler_cfg(cfg_env_with_handler_cfg)
|
||||
.with_block_env(evm_env.block_env)
|
||||
// add additional precompiles
|
||||
.append_handler_register(MyEvmConfig::set_precompiles)
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn evm_with_env_and_inspector<DB, I>(
|
||||
&self,
|
||||
db: DB,
|
||||
evm_env: EvmEnv,
|
||||
inspector: I,
|
||||
) -> Self::Evm<'_, DB, I>
|
||||
where
|
||||
DB: Database,
|
||||
I: GetInspector<DB>,
|
||||
{
|
||||
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg {
|
||||
cfg_env: evm_env.cfg_env,
|
||||
handler_cfg: HandlerCfg::new(evm_env.spec),
|
||||
};
|
||||
|
||||
EvmBuilder::default()
|
||||
.with_db(db)
|
||||
.with_external_context(inspector)
|
||||
.with_cfg_env_with_handler_cfg(cfg_env_with_handler_cfg)
|
||||
.with_block_env(evm_env.block_env)
|
||||
// add additional precompiles
|
||||
.append_handler_register(MyEvmConfig::set_precompiles)
|
||||
.append_handler_register(inspector_handle_register)
|
||||
.build()
|
||||
.into()
|
||||
fn evm_factory(&self) -> &Self::EvmFactory {
|
||||
&self.evm_factory
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,6 +187,70 @@ where
|
||||
self.inner.build(MyEvmConfig::new(ctx.chain_spec()), ctx, pool)
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom precompile that contains static precompiles.
|
||||
#[derive(Clone)]
|
||||
pub struct CustomPrecompiles<CTX> {
|
||||
pub precompiles: EthPrecompiles<CTX>,
|
||||
}
|
||||
|
||||
impl<CTX: ContextTr> CustomPrecompiles<CTX> {
|
||||
/// 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.
|
||||
pub fn prague_custom() -> &'static Precompiles {
|
||||
static INSTANCE: OnceLock<Precompiles> = OnceLock::new();
|
||||
INSTANCE.get_or_init(|| {
|
||||
let mut precompiles = Precompiles::prague().clone();
|
||||
// Custom precompile.
|
||||
precompiles.extend([(
|
||||
address!("0000000000000000000000000000000000000999"),
|
||||
|_, _| -> PrecompileResult {
|
||||
PrecompileResult::Ok(PrecompileOutput::new(0, Bytes::new()))
|
||||
} as PrecompileFn,
|
||||
)
|
||||
.into()]);
|
||||
precompiles
|
||||
})
|
||||
}
|
||||
|
||||
impl<CTX: ContextTr> PrecompileProvider for CustomPrecompiles<CTX> {
|
||||
type Context = CTX;
|
||||
type Output = InterpreterResult;
|
||||
|
||||
fn set_spec(&mut self, spec: <<Self::Context as ContextTr>::Cfg as Cfg>::Spec) {
|
||||
let spec_id = spec.clone().into();
|
||||
if spec_id == SpecId::PRAGUE {
|
||||
self.precompiles = EthPrecompiles { precompiles: prague_custom(), ..Default::default() }
|
||||
} else {
|
||||
self.precompiles.set_spec(spec);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
context: &mut Self::Context,
|
||||
address: &Address,
|
||||
bytes: &Bytes,
|
||||
gas_limit: u64,
|
||||
) -> Result<Option<Self::Output>, reth::revm::precompile::PrecompileErrors> {
|
||||
self.precompiles.run(context, address, bytes, gas_limit)
|
||||
}
|
||||
|
||||
fn contains(&self, address: &Address) -> bool {
|
||||
self.precompiles.contains(address)
|
||||
}
|
||||
|
||||
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address> + '_> {
|
||||
self.precompiles.warm_addresses()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
let _guard = RethTracer::new().init()?;
|
||||
|
||||
@ -9,9 +9,9 @@ license.workspace = true
|
||||
reth.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
revm-primitives.workspace = true
|
||||
alloy-rpc-types-eth.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
futures-util.workspace = true
|
||||
alloy-evm.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-eips.workspace = true
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use alloy_eips::BlockNumberOrTag;
|
||||
use alloy_evm::Evm;
|
||||
use alloy_primitives::Address;
|
||||
use alloy_rpc_types_eth::{state::EvmOverrides, TransactionRequest};
|
||||
use clap::Parser;
|
||||
@ -20,17 +21,16 @@ use reth::{
|
||||
chainspec::EthereumChainSpecParser,
|
||||
cli::Cli,
|
||||
revm::{
|
||||
inspector_handle_register,
|
||||
interpreter::{Interpreter, OpCode},
|
||||
primitives::{Env, EnvWithHandlerCfg},
|
||||
Database, Evm, EvmContext, Inspector,
|
||||
bytecode::opcode::OpCode,
|
||||
context_interface::ContextTr,
|
||||
inspector::Inspector,
|
||||
interpreter::{interpreter::EthInterpreter, interpreter_types::Jumps, Interpreter},
|
||||
},
|
||||
rpc::api::eth::helpers::Call,
|
||||
transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_evm::EvmEnv;
|
||||
use reth_evm::ConfigureEvm;
|
||||
use reth_node_ethereum::node::EthereumNode;
|
||||
use revm_primitives::HandlerCfg;
|
||||
|
||||
fn main() {
|
||||
Cli::<EthereumChainSpecParser, RethCliTxpoolExt>::parse()
|
||||
@ -60,31 +60,24 @@ fn main() {
|
||||
let call_request =
|
||||
TransactionRequest::from_recovered_transaction(tx.to_consensus());
|
||||
|
||||
let evm_config = node.evm_config.clone();
|
||||
|
||||
let result = eth_api
|
||||
.spawn_with_call_at(
|
||||
call_request,
|
||||
BlockNumberOrTag::Latest.into(),
|
||||
EvmOverrides::default(),
|
||||
move |db, evm_env, tx_env| {
|
||||
let EvmEnv { cfg_env, block_env, spec } = evm_env;
|
||||
let env = EnvWithHandlerCfg {
|
||||
handler_cfg: HandlerCfg::new(spec),
|
||||
env: Env::boxed(cfg_env, block_env, tx_env),
|
||||
};
|
||||
let mut dummy_inspector = DummyInspector::default();
|
||||
{
|
||||
// configure the evm with the custom inspector
|
||||
let mut evm = Evm::builder()
|
||||
.with_db(db)
|
||||
.with_external_context(&mut dummy_inspector)
|
||||
.with_env_with_handler_cfg(env)
|
||||
.append_handler_register(inspector_handle_register)
|
||||
.build();
|
||||
// execute the transaction on a blocking task and await
|
||||
// the
|
||||
// inspector result
|
||||
let _ = evm.transact()?;
|
||||
}
|
||||
let mut evm = evm_config.evm_with_env_and_inspector(
|
||||
db,
|
||||
evm_env,
|
||||
&mut dummy_inspector,
|
||||
);
|
||||
// execute the transaction on a blocking task and await
|
||||
// the
|
||||
// inspector result
|
||||
let _ = evm.transact(tx_env)?;
|
||||
Ok(dummy_inspector)
|
||||
},
|
||||
)
|
||||
@ -130,16 +123,16 @@ struct DummyInspector {
|
||||
ret_val: Vec<String>,
|
||||
}
|
||||
|
||||
impl<DB> Inspector<DB> for DummyInspector
|
||||
impl<CTX> Inspector<CTX, EthInterpreter> for DummyInspector
|
||||
where
|
||||
DB: Database,
|
||||
CTX: ContextTr,
|
||||
{
|
||||
/// This method is called at each step of the EVM execution.
|
||||
/// It checks if the current opcode is valid and if so, it stores the opcode and its
|
||||
/// corresponding program counter in the `ret_val` vector.
|
||||
fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext<DB>) {
|
||||
if let Some(opcode) = OpCode::new(interp.current_opcode()) {
|
||||
self.ret_val.push(format!("{}: {}", interp.program_counter(), opcode));
|
||||
fn step(&mut self, interp: &mut Interpreter<EthInterpreter>, _context: &mut CTX) {
|
||||
if let Some(opcode) = OpCode::new(interp.bytecode.opcode()) {
|
||||
self.ret_val.push(format!("{}: {}", interp.bytecode.pc(), opcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ impl CustomRlpxProtoMessage {
|
||||
/// Decodes a `CustomRlpxProtoMessage` from the given message buffer.
|
||||
pub fn decode_message(buf: &mut &[u8]) -> Option<Self> {
|
||||
if buf.is_empty() {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
let id = buf[0];
|
||||
buf.advance(1);
|
||||
|
||||
@ -14,6 +14,7 @@ reth-primitives.workspace = true
|
||||
reth-node-ethereum = { workspace = true, features = ["test-utils"] }
|
||||
reth-tracing.workspace = true
|
||||
reth-evm.workspace = true
|
||||
alloy-evm.workspace = true
|
||||
alloy-genesis.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use alloy_consensus::Header;
|
||||
use alloy_evm::{eth::EthEvmContext, EvmFactory};
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::{Address, Bytes};
|
||||
use parking_lot::RwLock;
|
||||
@ -10,19 +11,22 @@ use reth::{
|
||||
api::NextBlockEnvAttributes,
|
||||
builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder},
|
||||
revm::{
|
||||
handler::register::EvmHandler,
|
||||
inspector_handle_register,
|
||||
precompile::{Precompile, PrecompileSpecId},
|
||||
primitives::{
|
||||
CfgEnvWithHandlerCfg, EVMError, Env, HaltReason, HandlerCfg, PrecompileResult, SpecId,
|
||||
StatefulPrecompileMut, TxEnv,
|
||||
context::{Cfg, Context, TxEnv},
|
||||
context_interface::{
|
||||
result::{EVMError, HaltReason},
|
||||
ContextTr,
|
||||
},
|
||||
ContextPrecompile, ContextPrecompiles, EvmBuilder, GetInspector,
|
||||
handler::{EthPrecompiles, PrecompileProvider},
|
||||
inspector::{Inspector, NoOpInspector},
|
||||
interpreter::{interpreter::EthInterpreter, InterpreterResult},
|
||||
precompile::PrecompileErrors,
|
||||
specification::hardfork::SpecId,
|
||||
MainBuilder, MainContext,
|
||||
},
|
||||
tasks::TaskManager,
|
||||
};
|
||||
use reth_chainspec::{Chain, ChainSpec};
|
||||
use reth_evm::{env::EvmEnv, Database};
|
||||
use reth_evm::{Database, EvmEnv};
|
||||
use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes, NodeTypes};
|
||||
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
|
||||
use reth_node_ethereum::{
|
||||
@ -35,10 +39,9 @@ use schnellru::{ByLength, LruMap};
|
||||
use std::{collections::HashMap, convert::Infallible, sync::Arc};
|
||||
|
||||
/// Type alias for the LRU cache used within the [`PrecompileCache`].
|
||||
type PrecompileLRUCache = LruMap<(Bytes, u64), PrecompileResult>;
|
||||
type PrecompileLRUCache = LruMap<(SpecId, Bytes, u64), Result<InterpreterResult, PrecompileErrors>>;
|
||||
|
||||
/// Type alias for the thread-safe `Arc<RwLock<_>>` wrapper around [`PrecompileCache`].
|
||||
type CachedPrecompileResult = Arc<RwLock<PrecompileLRUCache>>;
|
||||
type WrappedEthEvm<DB, I> = EthEvm<DB, I, WrappedPrecompile<EthPrecompiles<EthEvmContext<DB>>>>;
|
||||
|
||||
/// A cache for precompile inputs / outputs.
|
||||
///
|
||||
@ -50,7 +53,44 @@ type CachedPrecompileResult = Arc<RwLock<PrecompileLRUCache>>;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PrecompileCache {
|
||||
/// Caches for each precompile input / output.
|
||||
cache: HashMap<(Address, SpecId), CachedPrecompileResult>,
|
||||
cache: HashMap<Address, PrecompileLRUCache>,
|
||||
}
|
||||
|
||||
/// Custom EVM factory.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct MyEvmFactory {
|
||||
precompile_cache: Arc<RwLock<PrecompileCache>>,
|
||||
}
|
||||
|
||||
impl EvmFactory<EvmEnv> for MyEvmFactory {
|
||||
type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> = WrappedEthEvm<DB, I>;
|
||||
type Tx = TxEnv;
|
||||
type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
|
||||
type HaltReason = HaltReason;
|
||||
type Context<DB: Database> = EthEvmContext<DB>;
|
||||
|
||||
fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
|
||||
let new_cache = self.precompile_cache.clone();
|
||||
|
||||
let evm = Context::mainnet()
|
||||
.with_db(db)
|
||||
.with_cfg(input.cfg_env)
|
||||
.with_block(input.block_env)
|
||||
.build_mainnet_with_inspector(NoOpInspector {})
|
||||
.with_precompiles(WrappedPrecompile::new(EthPrecompiles::default(), new_cache));
|
||||
|
||||
EthEvm::new(evm, false)
|
||||
}
|
||||
|
||||
fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>, EthInterpreter>>(
|
||||
&self,
|
||||
db: DB,
|
||||
input: EvmEnv,
|
||||
inspector: I,
|
||||
) -> Self::Evm<DB, I> {
|
||||
EthEvm::new(self.create_evm(db, input).into_inner().with_inspector(inspector), true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom EVM configuration
|
||||
@ -58,93 +98,85 @@ pub struct PrecompileCache {
|
||||
#[non_exhaustive]
|
||||
pub struct MyEvmConfig {
|
||||
inner: EthEvmConfig,
|
||||
precompile_cache: Arc<RwLock<PrecompileCache>>,
|
||||
evm_factory: MyEvmFactory,
|
||||
}
|
||||
|
||||
impl MyEvmConfig {
|
||||
/// Creates a new instance.
|
||||
pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
Self { inner: EthEvmConfig::new(chain_spec), precompile_cache: Default::default() }
|
||||
}
|
||||
|
||||
/// Sets the precompiles to the EVM handler
|
||||
///
|
||||
/// This will be invoked when the EVM is created via [ConfigureEvm::evm_with_env] or
|
||||
/// [ConfigureEvm::evm_with_env_and_inspector]
|
||||
///
|
||||
/// This will use the default mainnet precompiles and wrap them with a cache.
|
||||
pub fn set_precompiles<EXT, DB>(
|
||||
handler: &mut EvmHandler<EXT, DB>,
|
||||
cache: Arc<RwLock<PrecompileCache>>,
|
||||
) where
|
||||
DB: Database,
|
||||
{
|
||||
// first we need the evm spec id, which determines the precompiles
|
||||
let spec_id = handler.cfg.spec_id;
|
||||
|
||||
let mut loaded_precompiles: ContextPrecompiles<DB> =
|
||||
ContextPrecompiles::new(PrecompileSpecId::from_spec_id(spec_id));
|
||||
for (address, precompile) in loaded_precompiles.to_mut().iter_mut() {
|
||||
// get or insert the cache for this address / spec
|
||||
let mut cache = cache.write();
|
||||
let cache = cache
|
||||
.cache
|
||||
.entry((*address, spec_id))
|
||||
.or_insert(Arc::new(RwLock::new(LruMap::new(ByLength::new(1024)))));
|
||||
|
||||
*precompile = Self::wrap_precompile(precompile.clone(), cache.clone());
|
||||
}
|
||||
|
||||
// install the precompiles
|
||||
handler.pre_execution.load_precompiles = Arc::new(move || loaded_precompiles.clone());
|
||||
}
|
||||
|
||||
/// Given a [`ContextPrecompile`] and cache for a specific precompile, create a new precompile
|
||||
/// that wraps the precompile with the cache.
|
||||
fn wrap_precompile<DB>(
|
||||
precompile: ContextPrecompile<DB>,
|
||||
cache: Arc<RwLock<LruMap<(Bytes, u64), PrecompileResult>>>,
|
||||
) -> ContextPrecompile<DB>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
let ContextPrecompile::Ordinary(precompile) = precompile else {
|
||||
// context stateful precompiles are not supported, due to lifetime issues or skill
|
||||
// issues
|
||||
panic!("precompile is not ordinary");
|
||||
};
|
||||
|
||||
let wrapped = WrappedPrecompile { precompile, cache: cache.clone() };
|
||||
|
||||
ContextPrecompile::Ordinary(Precompile::StatefulMut(Box::new(wrapped)))
|
||||
Self { inner: EthEvmConfig::new(chain_spec), evm_factory: MyEvmFactory::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom precompile that contains the cache and precompile it wraps.
|
||||
#[derive(Clone)]
|
||||
pub struct WrappedPrecompile {
|
||||
pub struct WrappedPrecompile<P: PrecompileProvider> {
|
||||
/// The precompile to wrap.
|
||||
precompile: Precompile,
|
||||
precompile: P,
|
||||
/// The cache to use.
|
||||
cache: Arc<RwLock<LruMap<(Bytes, u64), PrecompileResult>>>,
|
||||
cache: Arc<RwLock<PrecompileCache>>,
|
||||
/// The spec id to use.
|
||||
spec: SpecId,
|
||||
}
|
||||
|
||||
impl StatefulPrecompileMut for WrappedPrecompile {
|
||||
fn call_mut(&mut self, bytes: &Bytes, gas_price: u64, _env: &Env) -> PrecompileResult {
|
||||
impl<P: PrecompileProvider> WrappedPrecompile<P> {
|
||||
/// Given a [`PrecompileProvider`] and cache for a specific precompiles, create a
|
||||
/// wrapper that can be used inside Evm.
|
||||
fn new(precompile: P, cache: Arc<RwLock<PrecompileCache>>) -> Self {
|
||||
WrappedPrecompile { precompile, cache: cache.clone(), spec: SpecId::LATEST }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: PrecompileProvider<Output = InterpreterResult>> PrecompileProvider
|
||||
for WrappedPrecompile<P>
|
||||
{
|
||||
type Context = P::Context;
|
||||
type Output = P::Output;
|
||||
|
||||
fn set_spec(&mut self, spec: <<Self::Context as ContextTr>::Cfg as Cfg>::Spec) {
|
||||
self.precompile.set_spec(spec.clone());
|
||||
self.spec = spec.into();
|
||||
}
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
context: &mut Self::Context,
|
||||
address: &Address,
|
||||
bytes: &Bytes,
|
||||
gas_limit: u64,
|
||||
) -> Result<Option<Self::Output>, reth::revm::precompile::PrecompileErrors> {
|
||||
let mut cache = self.cache.write();
|
||||
let key = (bytes.clone(), gas_price);
|
||||
let key = (self.spec, bytes.clone(), gas_limit);
|
||||
|
||||
// get the result if it exists
|
||||
if let Some(result) = cache.get(&key) {
|
||||
return result.clone()
|
||||
if let Some(precompiles) = cache.cache.get_mut(address) {
|
||||
if let Some(result) = precompiles.get(&key) {
|
||||
return result.clone().map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
// call the precompile if cache miss
|
||||
let output = self.precompile.call(bytes, gas_price, _env);
|
||||
cache.insert(key, output.clone());
|
||||
let output = self.precompile.run(context, address, bytes, gas_limit);
|
||||
|
||||
if let Some(output) = output.clone().transpose() {
|
||||
// insert the result into the cache
|
||||
cache
|
||||
.cache
|
||||
.entry(*address)
|
||||
.or_insert(PrecompileLRUCache::new(ByLength::new(1024)))
|
||||
.insert(key, output);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn contains(&self, address: &Address) -> bool {
|
||||
self.precompile.contains(address)
|
||||
}
|
||||
|
||||
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address> + '_> {
|
||||
self.precompile.warm_addresses()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureEvmEnv for MyEvmConfig {
|
||||
@ -172,56 +204,10 @@ impl ConfigureEvmEnv for MyEvmConfig {
|
||||
}
|
||||
|
||||
impl ConfigureEvm for MyEvmConfig {
|
||||
type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>;
|
||||
type EvmError<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
|
||||
type HaltReason = HaltReason;
|
||||
type EvmFactory = MyEvmFactory;
|
||||
|
||||
fn evm_with_env<DB: Database>(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> {
|
||||
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg {
|
||||
cfg_env: evm_env.cfg_env,
|
||||
handler_cfg: HandlerCfg::new(evm_env.spec),
|
||||
};
|
||||
|
||||
let new_cache = self.precompile_cache.clone();
|
||||
EvmBuilder::default()
|
||||
.with_db(db)
|
||||
.with_cfg_env_with_handler_cfg(cfg_env_with_handler_cfg)
|
||||
.with_block_env(evm_env.block_env)
|
||||
// add additional precompiles
|
||||
.append_handler_register_box(Box::new(move |handler| {
|
||||
MyEvmConfig::set_precompiles(handler, new_cache.clone())
|
||||
}))
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn evm_with_env_and_inspector<DB, I>(
|
||||
&self,
|
||||
db: DB,
|
||||
evm_env: EvmEnv,
|
||||
inspector: I,
|
||||
) -> Self::Evm<'_, DB, I>
|
||||
where
|
||||
DB: Database,
|
||||
I: GetInspector<DB>,
|
||||
{
|
||||
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg {
|
||||
cfg_env: evm_env.cfg_env,
|
||||
handler_cfg: HandlerCfg::new(evm_env.spec),
|
||||
};
|
||||
let new_cache = self.precompile_cache.clone();
|
||||
EvmBuilder::default()
|
||||
.with_db(db)
|
||||
.with_external_context(inspector)
|
||||
.with_cfg_env_with_handler_cfg(cfg_env_with_handler_cfg)
|
||||
.with_block_env(evm_env.block_env)
|
||||
// add additional precompiles
|
||||
.append_handler_register_box(Box::new(move |handler| {
|
||||
MyEvmConfig::set_precompiles(handler, new_cache.clone())
|
||||
}))
|
||||
.append_handler_register(inspector_handle_register)
|
||||
.build()
|
||||
.into()
|
||||
fn evm_factory(&self) -> &Self::EvmFactory {
|
||||
&self.evm_factory
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +232,7 @@ where
|
||||
) -> eyre::Result<(Self::EVM, Self::Executor)> {
|
||||
let evm_config = MyEvmConfig {
|
||||
inner: EthEvmConfig::new(ctx.chain_spec()),
|
||||
precompile_cache: self.precompile_cache.clone(),
|
||||
evm_factory: MyEvmFactory { precompile_cache: self.precompile_cache.clone() },
|
||||
};
|
||||
Ok((
|
||||
evm_config.clone(),
|
||||
|
||||
Reference in New Issue
Block a user