Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update JS inspector #6129

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ alloy-sol-types = "0.6"
alloy-rlp = "0.3"
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy" }
alloy-rpc-trace-types = { git = "https://github.com/alloy-rs/alloy" }
alloy-rpc-engine-types = { git = "https://github.com/alloy-rs/alloy" }
alloy-trie = "0.2"
ethers-core = { version = "2.0", default-features = false }
ethers-providers = { version = "2.0", default-features = false }
Expand Down
8 changes: 1 addition & 7 deletions crates/rpc/rpc-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,7 +1119,6 @@ where
RethRpcModule::Debug => DebugApi::new(
self.provider.clone(),
eth_api.clone(),
Box::new(self.executor.clone()),
self.blocking_pool_guard.clone(),
)
.into_rpc()
Expand Down Expand Up @@ -1315,12 +1314,7 @@ where
/// If called outside of the tokio runtime. See also [Self::eth_api]
pub fn debug_api(&mut self) -> DebugApi<Provider, EthApi<Provider, Pool, Network>> {
let eth_api = self.eth_api();
DebugApi::new(
self.provider.clone(),
eth_api,
Box::new(self.executor.clone()),
self.blocking_pool_guard.clone(),
)
DebugApi::new(self.provider.clone(), eth_api, self.blocking_pool_guard.clone())
}

/// Instantiates NetApi
Expand Down
164 changes: 19 additions & 145 deletions crates/rpc/rpc/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::{
eth::{
error::{EthApiError, EthResult},
revm_utils::{
clone_into_empty_db, inspect, inspect_and_return_db, prepare_call_env,
replay_transactions_until, transact, EvmOverrides,
inspect, inspect_and_return_db, prepare_call_env, replay_transactions_until, transact,
EvmOverrides,
},
EthTransactions, TransactionSource,
},
Expand All @@ -15,21 +15,15 @@ use async_trait::async_trait;
use jsonrpsee::core::RpcResult;
use reth_primitives::{
revm::env::tx_env_with_recovered,
revm_primitives::{
db::{DatabaseCommit, DatabaseRef},
BlockEnv, CfgEnv,
},
revm_primitives::{db::DatabaseCommit, BlockEnv, CfgEnv},
Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256,
};
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderBox, TransactionVariant,
};
use reth_revm::{
database::{StateProviderDatabase, SubState},
tracing::{
js::{JsDbRequest, JsInspector},
FourByteInspector, TracingInspector, TracingInspectorConfig,
},
tracing::{js::JsInspector, FourByteInspector, TracingInspector, TracingInspectorConfig},
};
use reth_rpc_api::DebugApiServer;
use reth_rpc_types::{
Expand All @@ -39,14 +33,9 @@ use reth_rpc_types::{
},
BlockError, Bundle, CallRequest, RichBlock, StateContext,
};
use reth_tasks::TaskSpawner;
use revm::{
db::{CacheDB, EmptyDB},
primitives::Env,
};
use revm::{db::CacheDB, primitives::Env};
use std::sync::Arc;
use tokio::sync::{mpsc, AcquireError, OwnedSemaphorePermit};
use tokio_stream::{wrappers::ReceiverStream, StreamExt};
use tokio::sync::{AcquireError, OwnedSemaphorePermit};

/// `debug` API implementation.
///
Expand All @@ -59,14 +48,8 @@ pub struct DebugApi<Provider, Eth> {

impl<Provider, Eth> DebugApi<Provider, Eth> {
/// Create a new instance of the [DebugApi]
pub fn new(
provider: Provider,
eth: Eth,
task_spawner: Box<dyn TaskSpawner>,
blocking_task_guard: BlockingTaskGuard,
) -> Self {
let inner =
Arc::new(DebugApiInner { provider, eth_api: eth, task_spawner, blocking_task_guard });
pub fn new(provider: Provider, eth: Eth, blocking_task_guard: BlockingTaskGuard) -> Self {
let inner = Arc::new(DebugApiInner { provider, eth_api: eth, blocking_task_guard });
Self { inner }
}
}
Expand Down Expand Up @@ -106,7 +89,7 @@ where
let tx = tx_env_with_recovered(&tx);
let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx };
let (result, state_changes) =
this.trace_transaction(opts.clone(), env, at, &mut db).map_err(|err| {
this.trace_transaction(opts.clone(), env, &mut db).map_err(|err| {
results.push(TraceResult::Error {
error: err.to_string(),
tx_hash: Some(tx_hash),
Expand Down Expand Up @@ -238,7 +221,7 @@ where
)?;

let env = Env { cfg, block: block_env, tx: tx_env_with_recovered(&tx) };
this.trace_transaction(opts, env, state_at, &mut db).map(|(trace, _)| trace)
this.trace_transaction(opts, env, &mut db).map(|(trace, _)| trace)
})
.await
}
Expand Down Expand Up @@ -325,29 +308,16 @@ where
GethDebugTracerType::JsTracer(code) => {
let config = tracer_config.into_json();

// for JS tracing we need to setup all async work before we can start tracing
// because JSTracer and all JS types are not Send
let (_, _, at) = self.inner.eth_api.evm_env_at(at).await?;
let state = self.inner.eth_api.state_at(at)?;
let db = CacheDB::new(StateProviderDatabase::new(state));
let has_state_overrides = overrides.has_state();

// If the caller provided state overrides we need to clone the DB so the js
// service has access these modifications
let mut maybe_override_db = None;
if has_state_overrides {
maybe_override_db = Some(clone_into_empty_db(&db));
}

let to_db_service = self.spawn_js_trace_service(at, maybe_override_db)?;

let res = self
.inner
.eth_api
.spawn_with_call_at(call, at, overrides, move |db, env| {
let mut inspector = JsInspector::new(code, config, to_db_service)?;
let (res, _) = inspect(db, env.clone(), &mut inspector)?;
Ok(inspector.json_result(res, &env)?)
let mut inspector = JsInspector::new(code, config)?;
let (res, _, db) =
inspect_and_return_db(db, env.clone(), &mut inspector)?;
Ok(inspector.json_result(res, &env, &db)?)
})
.await?;

Expand Down Expand Up @@ -459,12 +429,8 @@ where
overrides,
)?;

let (trace, state) = this.trace_transaction(
tracing_options.clone(),
env,
target_block,
&mut db,
)?;
let (trace, state) =
this.trace_transaction(tracing_options.clone(), env, &mut db)?;

// If there is more transactions, commit the database
// If there is no transactions, but more bundles, commit to the database too
Expand Down Expand Up @@ -492,7 +458,6 @@ where
&self,
opts: GethDebugTracingOptions,
env: Env,
at: BlockId,
db: &mut SubState<StateProviderBox>,
) -> EthResult<(GethTrace, revm_primitives::State)> {
let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
Expand Down Expand Up @@ -551,18 +516,11 @@ where
GethDebugTracerType::JsTracer(code) => {
let config = tracer_config.into_json();

// We need to clone the database because the JS tracer will need to access the
// current state via the spawned service
let js_db = clone_into_empty_db(db);
// we spawn the database service that will be used by the JS tracer
// transaction because the service needs access to the committed state changes
let to_db_service = self.spawn_js_trace_service(at, Some(js_db))?;

let mut inspector = JsInspector::new(code, config, to_db_service)?;
let (res, env) = inspect(db, env, &mut inspector)?;
let mut inspector = JsInspector::new(code, config)?;
let (res, env, db) = inspect_and_return_db(db, env, &mut inspector)?;

let state = res.state.clone();
let result = inspector.json_result(res, &env)?;
let result = inspector.json_result(res, &env, db)?;
Ok((GethTrace::JS(result), state))
}
}
Expand All @@ -580,88 +538,6 @@ where

Ok((frame.into(), res.state))
}

/// Spawns [Self::js_trace_db_service_task] on a new task and returns a channel to send requests
/// to it.
///
/// Note: This blocks until the service is ready to receive requests.
fn spawn_js_trace_service(
&self,
at: BlockId,
db: Option<CacheDB<EmptyDB>>,
) -> EthResult<mpsc::Sender<JsDbRequest>> {
let (to_db_service, rx) = mpsc::channel(1);
let (ready_tx, ready_rx) = std::sync::mpsc::channel();
let this = self.clone();
// this needs to be on a blocking task because it only does blocking work besides waiting
// for db requests
self.inner.task_spawner.spawn_blocking(Box::pin(async move {
this.js_trace_db_service_task(at, rx, ready_tx, db).await
}));
// wait for initialization
ready_rx.recv().map_err(|_| {
EthApiError::InternalJsTracerError("js tracer initialization failed".to_string())
})??;
Ok(to_db_service)
}

/// A services that handles database requests issued from inside the JavaScript tracing engine.
///
/// If this traces with modified state, this takes a `db` parameter that contains the modified
/// in memory state. This is required because [StateProviderBox] can not be cloned or shared
/// across threads.
async fn js_trace_db_service_task(
self,
at: BlockId,
rx: mpsc::Receiver<JsDbRequest>,
on_ready: std::sync::mpsc::Sender<EthResult<()>>,
db: Option<CacheDB<EmptyDB>>,
) {
let state = match self.inner.eth_api.state_at(at) {
Ok(state) => {
let _ = on_ready.send(Ok(()));
state
}
Err(err) => {
let _ = on_ready.send(Err(err));
return
}
};

let db = if let Some(db) = db {
let CacheDB { accounts, contracts, logs, block_hashes, .. } = db;
CacheDB {
accounts,
contracts,
logs,
block_hashes,
db: StateProviderDatabase::new(state),
}
} else {
CacheDB::new(StateProviderDatabase::new(state))
};

let mut stream = ReceiverStream::new(rx);
while let Some(req) = stream.next().await {
match req {
JsDbRequest::Basic { address, resp } => {
let acc = db.basic_ref(address).map_err(|err| err.to_string());
let _ = resp.send(acc);
}
JsDbRequest::Code { code_hash, resp } => {
let code = db
.code_by_hash_ref(code_hash)
.map(|code| code.bytecode)
.map_err(|err| err.to_string());
let _ = resp.send(code);
}
JsDbRequest::StorageAt { address, index, resp } => {
let value = db.storage_ref(address, index).map_err(|err| err.to_string());
let _ = resp.send(value);
}
}
}
}
}

#[async_trait]
Expand Down Expand Up @@ -1054,6 +930,4 @@ struct DebugApiInner<Provider, Eth> {
eth_api: Eth,
// restrict the number of concurrent calls to blocking calls
blocking_task_guard: BlockingTaskGuard,
/// The type that can spawn tasks which would otherwise block.
task_spawner: Box<dyn TaskSpawner>,
}
18 changes: 1 addition & 17 deletions crates/rpc/rpc/src/eth/revm_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use reth_rpc_types::{
BlockOverrides, CallRequest,
};
use revm::{
db::{CacheDB, EmptyDB},
db::CacheDB,
precompile::{Precompiles, SpecId as PrecompilesSpecId},
primitives::{BlockEnv, CfgEnv, Env, ResultAndState, SpecId, TransactTo, TxEnv},
Database, Inspector,
Expand Down Expand Up @@ -587,22 +587,6 @@ where
Ok(())
}

/// This clones and transforms the given [CacheDB] with an arbitrary [DatabaseRef] into a new
/// [CacheDB] with [EmptyDB] as the database type
#[inline]
pub(crate) fn clone_into_empty_db<DB>(db: &CacheDB<DB>) -> CacheDB<EmptyDB>
where
DB: DatabaseRef,
{
CacheDB {
accounts: db.accounts.clone(),
contracts: db.contracts.clone(),
logs: db.logs.clone(),
block_hashes: db.block_hashes.clone(),
db: Default::default(),
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading