diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index ddff1dd..711f1ec 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -41,6 +41,8 @@ tower-http = { version = "0.6.2", features = ["cors"] } tracing.workspace = true serde_json.workspace = true futures-util = "0.3.31" +itertools.workspace = true +revm-inspectors = "0.26.5" [dev-dependencies] signet-zenith.workspace = true diff --git a/crates/rpc/src/ctx/full.rs b/crates/rpc/src/ctx/full.rs index 42495f9..806e2e2 100644 --- a/crates/rpc/src/ctx/full.rs +++ b/crates/rpc/src/ctx/full.rs @@ -1,11 +1,9 @@ use crate::{RuRevmState, SignetCtx}; -use alloy::{ - consensus::{BlockHeader, Header}, - eips::BlockId, -}; +use alloy::{consensus::Header, eips::BlockId}; use reth::{ providers::{ProviderFactory, ProviderResult, providers::BlockchainProvider}, rpc::server_types::eth::{EthApiError, EthConfig}, + rpc::types::BlockNumberOrTag, tasks::{TaskExecutor, TaskSpawner}, }; use reth_node_api::FullNodeComponents; @@ -14,6 +12,64 @@ use signet_node_types::Pnt; use signet_tx_cache::client::TxCache; use signet_types::constants::SignetSystemConstants; use std::sync::Arc; +use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore}; +use trevm::{helpers::Ctx, revm::Inspector}; + +/// State location when instantiating an EVM instance. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i8)] +pub enum LoadState { + /// Load the state before the block's transactions (i.e. at the start of + /// the block). + Before = -1, + /// Load the state after the block's transactions (i.e. at the end of the + /// block). + After = 0, +} + +impl LoadState { + /// Adjust the height based on the state location. + pub const fn adjust_height(&self, height: u64) -> u64 { + match self { + LoadState::Before => height.saturating_sub(1), + LoadState::After => height, + } + } + + /// Returns `true` if the state location is before the block. + pub const fn is_before_block(&self) -> bool { + matches!(self, Self::Before) + } + + /// Returns `true` if the state location is after the block. + pub const fn is_after_block(&self) -> bool { + matches!(self, Self::After) + } +} + +impl From for LoadState { + fn from(value: BlockId) -> Self { + match value { + BlockId::Number(no) => no.into(), + _ => LoadState::After, + } + } +} + +impl From for LoadState { + fn from(value: BlockNumberOrTag) -> Self { + match value { + BlockNumberOrTag::Pending => LoadState::Before, + _ => LoadState::After, + } + } +} + +impl From for bool { + fn from(value: LoadState) -> Self { + matches!(value, LoadState::Before) + } +} /// RPC context. Contains all necessary host and signet components for serving /// RPC requests. @@ -82,6 +138,12 @@ where } } +/// Shared context between all RPC handlers. +#[derive(Debug)] +struct SharedContext { + tracing_semaphores: Arc, +} + /// Inner context for [`RpcCtx`]. #[derive(Debug)] pub struct RpcCtxInner @@ -91,6 +153,8 @@ where { host: Host, signet: SignetCtx, + + shared: SharedContext, } impl RpcCtxInner @@ -122,8 +186,20 @@ where where Tasks: TaskSpawner + Clone + 'static, { - SignetCtx::new(constants, factory, provider, eth_config, tx_cache, spawner) - .map(|signet| Self { host, signet }) + SignetCtx::new(constants, factory, provider, eth_config, tx_cache, spawner).map(|signet| { + Self { + host, + signet, + shared: SharedContext { + tracing_semaphores: Semaphore::new(eth_config.max_tracing_requests).into(), + }, + } + }) + } + + /// Acquire a permit for tracing. + pub async fn acquire_tracing_permit(&self) -> Result { + self.shared.tracing_semaphores.clone().acquire_owned().await } pub const fn host(&self) -> &Host { @@ -138,26 +214,45 @@ where self.host.task_executor() } - /// Create a trevm instance. - pub fn trevm( + /// Instantiate a trevm instance with a custom inspector. + /// + /// The `header` argument is used to fill the block context of the EVM. If + /// the `block_id` is `Pending` the EVM state will be the block BEFORE the + /// `header`. I.e. if the block number of the `header` is `n`, the state + /// will be after block `n-1`, (effectively the state at the start of block + /// `n`). + /// + /// if the `block_id` is `Pending` the state will be based on the + /// and `block` arguments + pub fn trevm_with_inspector>>( &self, - block_id: BlockId, - block: &Header, - ) -> Result, EthApiError> { - // decrement if the id is pending, so that the state is on the latest block - let height = block.number() - block_id.is_pending() as u64; - let spec_id = self.signet.evm_spec_id(block); + state: LoadState, + header: &Header, + inspector: I, + ) -> Result, EthApiError> { + let load_height = state.adjust_height(header.number); + let spec_id = self.signet.evm_spec_id(header); - let db = self.signet.state_provider_database(height)?; + let db = self.signet.state_provider_database(load_height)?; - let mut trevm = signet_evm::signet_evm(db, self.signet.constants().clone()) - .fill_cfg(&self.signet) - .fill_block(block); + let mut trevm = + signet_evm::signet_evm_with_inspector(db, inspector, self.signet.constants().clone()) + .fill_cfg(&self.signet) + .fill_block(header); trevm.set_spec_id(spec_id); Ok(trevm) } + + /// Create a trevm instance. + pub fn trevm( + &self, + state: LoadState, + header: &Header, + ) -> Result, EthApiError> { + self.trevm_with_inspector(state, header, trevm::revm::inspector::NoOpInspector) + } } // Some code in this file has been copied and modified from reth diff --git a/crates/rpc/src/ctx/mod.rs b/crates/rpc/src/ctx/mod.rs index b924288..af0d93e 100644 --- a/crates/rpc/src/ctx/mod.rs +++ b/crates/rpc/src/ctx/mod.rs @@ -2,7 +2,7 @@ mod signet; pub use signet::SignetCtx; mod full; -pub use full::RpcCtx; +pub use full::{LoadState, RpcCtx}; mod fee_hist; pub(crate) use fee_hist::strip_signet_system_txns; diff --git a/crates/rpc/src/debug/endpoints.rs b/crates/rpc/src/debug/endpoints.rs new file mode 100644 index 0000000..a899c4c --- /dev/null +++ b/crates/rpc/src/debug/endpoints.rs @@ -0,0 +1,170 @@ +use crate::{ + DebugError, RpcCtx, + utils::{await_handler, response_tri}, +}; +use ajj::{HandlerCtx, ResponsePayload}; +use alloy::{consensus::BlockHeader, eips::BlockId, primitives::B256}; +use itertools::Itertools; +use reth::rpc::{ + server_types::eth::EthApiError, + types::{ + TransactionInfo, + trace::geth::{GethDebugTracingOptions, GethTrace, TraceResult}, + }, +}; +use reth_node_api::FullNodeComponents; +use signet_evm::EvmErrored; +use signet_node_types::Pnt; +use signet_types::MagicSig; +use tracing::Instrument; + +/// Params for the `debug_traceBlockByNumber` and `debug_traceBlockByHash` +/// endpoints. +#[derive(Debug, serde::Deserialize)] +pub(super) struct TraceBlockParams(T, #[serde(default)] Option); + +/// Params type for `debug_traceTransaction`.` +#[derive(Debug, serde::Deserialize)] +pub(super) struct TraceTransactionParams(B256, #[serde(default)] Option); + +/// `debug_traceBlockByNumber` and `debug_traceBlockByHash` endpoint handler. +pub(super) async fn trace_block( + hctx: HandlerCtx, + TraceBlockParams(id, opts): TraceBlockParams, + ctx: RpcCtx, +) -> ResponsePayload, DebugError> +where + T: Into, + Host: FullNodeComponents, + Signet: Pnt, +{ + let _permit = response_tri!( + ctx.acquire_tracing_permit() + .await + .map_err(|_| DebugError::rpc_error("Failed to acquire tracing permit".into())) + ); + + let id = id.into(); + let span = tracing::debug_span!("traceBlock", ?id, tracer = ?opts.as_ref().and_then(|o| o.tracer.as_ref())); + + let fut = async move { + // Fetch the block by ID + let Some((hash, block)) = response_tri!(ctx.signet().raw_block(id).await) else { + return ResponsePayload::internal_error_message( + EthApiError::HeaderNotFound(id).to_string().into(), + ); + }; + + tracing::debug!(number = block.number(), "Loaded block"); + + // Allocate space for the frames + let mut frames = Vec::with_capacity(block.transaction_count()); + + // Instantiate the EVM with the block + let mut trevm = response_tri!(ctx.trevm(crate::LoadState::Before, block.header())); + + // Apply all transactions in the block up, tracing each one + let opts = opts.unwrap_or_default(); + + tracing::trace!(?opts, "Tracing block transactions"); + + let mut txns = block.body().transactions().enumerate().peekable(); + for (idx, tx) in txns + .by_ref() + .peeking_take_while(|(_, t)| MagicSig::try_from_signature(t.signature()).is_none()) + { + let tx_info = TransactionInfo { + hash: Some(*tx.hash()), + index: Some(idx as u64), + block_hash: Some(hash), + block_number: Some(block.header().number()), + base_fee: block.header().base_fee_per_gas(), + }; + + let t = trevm.fill_tx(tx); + + let frame; + (frame, trevm) = response_tri!(crate::debug::tracer::trace(t, &opts, tx_info)); + frames.push(TraceResult::Success { result: frame, tx_hash: Some(*tx.hash()) }); + + tracing::debug!(tx_index = idx, tx_hash = ?tx.hash(), "Traced transaction"); + } + + ResponsePayload::Success(frames) + } + .instrument(span); + + await_handler!(@response_option hctx.spawn_blocking(fut)) +} + +/// Handle for `debug_traceTransaction`. +pub(super) async fn trace_transaction( + hctx: HandlerCtx, + TraceTransactionParams(tx_hash, opts): TraceTransactionParams, + ctx: RpcCtx, +) -> ResponsePayload +where + Host: FullNodeComponents, + Signet: Pnt, +{ + let _permit = response_tri!( + ctx.acquire_tracing_permit() + .await + .map_err(|_| DebugError::rpc_error("Failed to acquire tracing permit".into())) + ); + + let span = tracing::debug_span!("traceTransaction", %tx_hash, tracer = ?opts.as_ref().and_then(|o| o.tracer.as_ref())); + + let fut = async move { + // Load the transaction by hash + let (tx, meta) = response_tri!( + response_tri!(ctx.signet().raw_transaction_by_hash(tx_hash)) + .ok_or(EthApiError::TransactionNotFound) + ); + + tracing::debug!("Loaded transaction metadata"); + + // Load the block containing the transaction + let res = response_tri!(ctx.signet().raw_block(meta.block_hash).await); + let (_, block) = + response_tri!(res.ok_or_else(|| EthApiError::HeaderNotFound(meta.block_hash.into()))); + + tracing::debug!(number = block.number(), "Loaded containing block"); + + // Load trevm at the start of the block (i.e. before any transactions are applied) + let mut trevm = response_tri!(ctx.trevm(crate::LoadState::Before, block.header())); + + // Apply all transactions in the block up to (but not including) the + // target one + let mut txns = block.body().transactions().enumerate().peekable(); + for (_idx, tx) in txns.by_ref().peeking_take_while(|(_, t)| t.hash() != tx.hash()) { + if MagicSig::try_from_signature(tx.signature()).is_some() { + return ResponsePayload::internal_error_message( + EthApiError::TransactionNotFound.to_string().into(), + ); + } + + trevm = response_tri!(trevm.run_tx(tx).map_err(EvmErrored::into_error)).accept_state(); + } + + let (index, tx) = response_tri!(txns.next().ok_or(EthApiError::TransactionNotFound)); + + let trevm = trevm.fill_tx(tx); + + let tx_info = TransactionInfo { + hash: Some(*tx.hash()), + index: Some(index as u64), + block_hash: Some(block.hash()), + block_number: Some(block.header().number()), + base_fee: block.header().base_fee_per_gas(), + }; + + let res = + response_tri!(crate::debug::tracer::trace(trevm, &opts.unwrap_or_default(), tx_info)).0; + + ResponsePayload::Success(res) + } + .instrument(span); + + await_handler!(@response_option hctx.spawn_blocking(fut)) +} diff --git a/crates/rpc/src/debug/error.rs b/crates/rpc/src/debug/error.rs new file mode 100644 index 0000000..c4fde13 --- /dev/null +++ b/crates/rpc/src/debug/error.rs @@ -0,0 +1,60 @@ +use reth::{ + providers::ProviderError, + rpc::{eth::filter::EthFilterError, server_types::eth::EthApiError}, +}; +use std::borrow::Cow; + +/// Errors that can occur when interacting with the `eth_` namespace. +#[derive(Debug, thiserror::Error, Clone)] +pub enum DebugError { + /// Provider error: [`ProviderError`]. + #[error("Provider error: {0}")] + Provider(#[from] ProviderError), + /// Filter error [`EthFilterError`]. + #[error("Filter error: {0}")] + Filter(Cow<'static, str>), + /// Eth API error: [`EthApiError`]. + #[error("Eth API error: {0}")] + Rpc(Cow<'static, str>), +} + +impl DebugError { + /// Create a new filter error. + pub const fn filter_error(msg: Cow<'static, str>) -> Self { + Self::Filter(msg) + } + + /// Create a new RPC error. + pub const fn rpc_error(msg: Cow<'static, str>) -> Self { + Self::Rpc(msg) + } +} + +impl From for DebugError { + fn from(err: EthFilterError) -> Self { + Self::filter_error(err.to_string().into()) + } +} + +impl From for DebugError { + fn from(err: EthApiError) -> Self { + Self::rpc_error(err.to_string().into()) + } +} + +impl DebugError { + /// Turn into a string by value, allows for `.map_err(EthError::to_string)` + /// to be used. + pub fn into_string(self) -> String { + ToString::to_string(&self) + } +} + +impl serde::Serialize for DebugError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} diff --git a/crates/rpc/src/debug/mod.rs b/crates/rpc/src/debug/mod.rs new file mode 100644 index 0000000..5a8648e --- /dev/null +++ b/crates/rpc/src/debug/mod.rs @@ -0,0 +1,24 @@ +mod endpoints; +use endpoints::*; + +mod error; +pub use error::DebugError; + +mod tracer; + +use crate::ctx::RpcCtx; +use alloy::{eips::BlockNumberOrTag, primitives::B256}; +use reth_node_api::FullNodeComponents; +use signet_node_types::Pnt; + +/// Instantiate a `debug` API router. +pub fn debug() -> ajj::Router> +where + Host: FullNodeComponents, + Signet: Pnt, +{ + ajj::Router::new() + .route("traceBlockByNumber", trace_block::) + .route("traceBlockByHash", trace_block::) + .route("traceTransaction", trace_transaction) +} diff --git a/crates/rpc/src/debug/tracer.rs b/crates/rpc/src/debug/tracer.rs new file mode 100644 index 0000000..0213c68 --- /dev/null +++ b/crates/rpc/src/debug/tracer.rs @@ -0,0 +1,239 @@ +//! This file is largely adapted from reth: `crates/rpc/rpc/src/debug.rs` +//! +//! In particular the `debug_trace_call` function. + +use crate::DebugError; +use reth::{ + revm::{DatabaseRef, context::ContextTr}, + rpc::{ + server_types::eth::EthApiError, + types::{ + TransactionInfo, + trace::geth::{ + FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, + GethDebugTracerType, GethDebugTracingOptions, GethTrace, NoopFrame, + }, + }, + }, +}; +use revm_inspectors::tracing::{ + FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, +}; +use signet_evm::{EvmNeedsTx, EvmReady}; +use tracing::instrument; +use trevm::{ + helpers::Ctx, + revm::{Database, DatabaseCommit, Inspector}, +}; + +/// Trace a transaction using the provided EVM and tracing options. +#[instrument(skip(trevm, config, tx_info), fields(tx_hash = ?tx_info.hash))] +pub(super) fn trace( + trevm: EvmReady, + config: &GethDebugTracingOptions, + tx_info: TransactionInfo, +) -> Result<(GethTrace, EvmNeedsTx), DebugError> +where + Db: Database + DatabaseCommit + DatabaseRef, + Insp: Inspector>, +{ + let Some(tracer) = &config.tracer else { return Err(EthApiError::InvalidTracerConfig.into()) }; + + let GethDebugTracerType::BuiltInTracer(built_in) = tracer else { + return Err(EthApiError::Unsupported("JS tracer").into()); + }; + + match built_in { + GethDebugBuiltInTracerType::FourByteTracer => trace_four_byte(trevm).map_err(Into::into), + GethDebugBuiltInTracerType::CallTracer => { + trace_call(&config.tracer_config, trevm).map_err(Into::into) + } + GethDebugBuiltInTracerType::FlatCallTracer => { + trace_flat_call(&config.tracer_config, trevm, tx_info).map_err(Into::into) + } + GethDebugBuiltInTracerType::PreStateTracer => { + trace_pre_state(&config.tracer_config, trevm).map_err(Into::into) + } + GethDebugBuiltInTracerType::NoopTracer => Ok(( + NoopFrame::default().into(), + trevm + .run() + .map_err(|err| EthApiError::EvmCustom(err.into_error().to_string()))? + .accept_state(), + )), + GethDebugBuiltInTracerType::MuxTracer => { + trace_mux(&config.tracer_config, trevm, tx_info).map_err(Into::into) + } + } +} + +/// Traces a call using [`GethDebugBuiltInTracerType::FourByteTracer`]. +fn trace_four_byte( + trevm: EvmReady, +) -> Result<(GethTrace, EvmNeedsTx), EthApiError> +where + Db: Database + DatabaseCommit, + Insp: Inspector>, +{ + let mut four_byte = FourByteInspector::default(); + + let trevm = trevm.try_with_inspector(&mut four_byte, |trevm| trevm.run()); + + let trevm = trevm.map_err(|e| EthApiError::EvmCustom(e.into_error().to_string()))?; + + Ok((FourByteFrame::from(four_byte).into(), trevm.accept_state())) +} + +/// Traces a call using [`GethDebugBuiltInTracerType::CallTracer`]. +fn trace_call( + tracer_config: &GethDebugTracerConfig, + trevm: EvmReady, +) -> Result<(GethTrace, EvmNeedsTx), EthApiError> +where + Db: Database + DatabaseCommit, + Insp: Inspector>, +{ + let call_config = + tracer_config.clone().into_call_config().map_err(|_| EthApiError::InvalidTracerConfig)?; + + let mut inspector = + TracingInspector::new(TracingInspectorConfig::from_geth_call_config(&call_config)); + + let trevm = trevm.try_with_inspector(&mut inspector, |trevm| trevm.run()); + + let trevm = trevm.map_err(|e| EthApiError::EvmCustom(e.into_error().to_string()))?; + + let frame = inspector + .with_transaction_gas_limit(trevm.gas_limit()) + .into_geth_builder() + .geth_call_traces(call_config, trevm.gas_used()); + + Ok((frame.into(), trevm.accept_state())) +} + +/// Traces a call using [`GethDebugBuiltInTracerType::PreStateTracer`] +fn trace_pre_state( + tracer_config: &GethDebugTracerConfig, + trevm: EvmReady, +) -> Result<(GethTrace, EvmNeedsTx), EthApiError> +where + Db: Database + DatabaseCommit + DatabaseRef, + Insp: Inspector>, +{ + let prestate_config = tracer_config + .clone() + .into_pre_state_config() + .map_err(|_| EthApiError::InvalidTracerConfig)?; + + let mut inspector = + TracingInspector::new(TracingInspectorConfig::from_geth_prestate_config(&prestate_config)); + + let trevm = trevm.try_with_inspector(&mut inspector, |trevm| trevm.run()); + + let trevm = trevm.map_err(|e| EthApiError::EvmCustom(e.into_error().to_string()))?; + let gas_limit = trevm.gas_limit(); + + // NB: Normally we would call `trevm.accept_state()` here, but we need the + // state after execution to be UNCOMMITED when we compute the prestate + // diffs. + let (result, mut trevm) = trevm.take_result_and_state(); + + let frame = inspector + .with_transaction_gas_limit(gas_limit) + .into_geth_builder() + .geth_prestate_traces(&result, &prestate_config, trevm.inner_mut_unchecked().db_mut()) + .map_err(|err| EthApiError::EvmCustom(err.to_string()))?; + + // This is equivalent to calling `trevm.accept_state()`. + trevm.inner_mut_unchecked().db_mut().commit(result.state); + + Ok((frame.into(), trevm)) +} + +fn trace_flat_call( + tracer_config: &GethDebugTracerConfig, + trevm: EvmReady, + tx_info: TransactionInfo, +) -> Result<(GethTrace, EvmNeedsTx), EthApiError> +where + Db: Database + DatabaseCommit, + Insp: Inspector>, +{ + let flat_call_config = tracer_config + .clone() + .into_flat_call_config() + .map_err(|_| EthApiError::InvalidTracerConfig)?; + + let mut inspector = + TracingInspector::new(TracingInspectorConfig::from_flat_call_config(&flat_call_config)); + + let trevm = trevm.try_with_inspector(&mut inspector, |trevm| trevm.run()); + + let trevm = trevm.map_err(|e| EthApiError::EvmCustom(e.into_error().to_string()))?; + + let frame = inspector + .with_transaction_gas_limit(trevm.gas_limit()) + .into_parity_builder() + .into_localized_transaction_traces(tx_info); + + Ok((frame.into(), trevm.accept_state())) +} + +fn trace_mux( + tracer_config: &GethDebugTracerConfig, + trevm: EvmReady, + tx_info: TransactionInfo, +) -> Result<(GethTrace, EvmNeedsTx), EthApiError> +where + Db: Database + DatabaseCommit + DatabaseRef, + Insp: Inspector>, +{ + let mux_config = + tracer_config.clone().into_mux_config().map_err(|_| EthApiError::InvalidTracerConfig)?; + + let mut inspector = MuxInspector::try_from_config(mux_config) + .map_err(|err| EthApiError::EvmCustom(err.to_string()))?; + + let trevm = trevm.try_with_inspector(&mut inspector, |trevm| trevm.run()); + let trevm = trevm.map_err(|e| EthApiError::EvmCustom(e.into_error().to_string()))?; + + // NB: Normally we would call `trevm.accept_state()` here, but we need the + // state after execution to be UNCOMMITED when we compute the prestate + // diffs. + let (result, mut trevm) = trevm.take_result_and_state(); + + let frame = inspector + .try_into_mux_frame(&result, trevm.inner_mut_unchecked().db_mut(), tx_info) + .map_err(|err| EthApiError::EvmCustom(err.to_string()))?; + + // This is equivalent to calling `trevm.accept_state()`. + trevm.inner_mut_unchecked().db_mut().commit(result.state); + + Ok((frame.into(), trevm)) +} + +// Some code in this file has been copied and modified from reth +// +// The original license is included below: +// +// The MIT License (MIT) +// +// Copyright (c) 2022-2025 Reth Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +//. +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. diff --git a/crates/rpc/src/eth/endpoints.rs b/crates/rpc/src/eth/endpoints.rs index d223418..eb94b40 100644 --- a/crates/rpc/src/eth/endpoints.rs +++ b/crates/rpc/src/eth/endpoints.rs @@ -3,7 +3,7 @@ use crate::{ eth::{CallErrorData, EthError}, interest::{FilterOutput, InterestKind}, receipts::build_signet_receipt, - utils::{await_jh_option, await_jh_option_response, response_tri}, + utils::{await_handler, response_tri}, }; use ajj::{HandlerCtx, ResponsePayload}; use alloy::{ @@ -143,7 +143,7 @@ where let id = t.into(); let task = async move { ctx.signet().block(id, full).await.map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn block_tx_count( @@ -159,7 +159,7 @@ where let id = t.into(); let task = async move { ctx.signet().tx_count(id).await.map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn block_receipts( @@ -210,7 +210,7 @@ where .map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn raw_transaction_by_hash( @@ -230,7 +230,7 @@ where .map(|tx| tx.as_ref().map(Encodable2718::encoded_2718).map(Into::into)) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn transaction_by_hash( @@ -244,7 +244,7 @@ where { let task = async move { ctx.signet().rpc_transaction_by_hash(hash).map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn raw_transaction_by_block_and_index( @@ -266,7 +266,7 @@ where Ok(block.body().transactions.get(index.to::()).map(|tx| tx.encoded_2718().into())) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn transaction_by_block_and_index( @@ -288,7 +288,7 @@ where .map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn transaction_receipt( @@ -303,7 +303,7 @@ where let task = async move { ctx.signet().rpc_receipt_by_hash(hash).await.map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn balance( @@ -322,7 +322,7 @@ where Ok(bal.unwrap_or_default()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn storage_at( @@ -341,7 +341,7 @@ where Ok(val.unwrap_or_default().to_be_bytes().into()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn addr_tx_count( @@ -360,7 +360,7 @@ where Ok(U64::from(count.unwrap_or_default())) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn code_at( @@ -379,7 +379,7 @@ where Ok(code.unwrap_or_default().original_bytes()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn header_by( @@ -394,7 +394,7 @@ where { let id = t.into(); - await_jh_option!(hctx.spawn_blocking_with_ctx(|hctx| async move { + await_handler!(@option hctx.spawn_blocking_with_ctx(|hctx| async move { Ok(block(hctx, BlockParams(id, None), ctx).await?.map(|block| block.header)) })) } @@ -448,7 +448,7 @@ where }; // Set up trevm - let trevm = response_tri!(ctx.trevm(id, &block_cfg)); + let trevm = response_tri!(ctx.trevm(id.into(), &block_cfg)); let mut trevm = response_tri!(trevm.maybe_apply_state_overrides(state_overrides.as_ref())) .maybe_apply_block_overrides(block_overrides.as_deref()) @@ -471,7 +471,7 @@ where } .instrument(span); - await_jh_option_response!(hctx.spawn_blocking(task)) + await_handler!(@response_option hctx.spawn_blocking(task)) } pub(super) async fn call( @@ -490,7 +490,7 @@ where let max_gas = ctx.signet().config().rpc_gas_cap; normalize_gas_stateless(&mut params.0, max_gas); - await_jh_option_response!(hctx.spawn_with_ctx(|hctx| async move { + await_handler!(@response_option hctx.spawn_with_ctx(|hctx| async move { let res = match run_call(hctx, params, ctx).await { ResponsePayload::Success(res) => res, ResponsePayload::Failure(err) => return ResponsePayload::Failure(err), @@ -554,7 +554,7 @@ where } }; - let trevm = response_tri!(ctx.trevm(id, &block_cfg)); + let trevm = response_tri!(ctx.trevm(id.into(), &block_cfg)); // Apply state and block overrides (state overrides are fallible as // they require DB access) @@ -587,7 +587,7 @@ where } .instrument(span); - await_jh_option_response!(hctx.spawn_blocking(task)) + await_handler!(@response_option hctx.spawn_blocking(task)) } pub(super) async fn gas_price( @@ -609,7 +609,7 @@ where Ok(suggested + U256::from(base_fee)) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn max_priority_fee_per_gas( @@ -623,7 +623,7 @@ where let task = async move { ctx.signet().gas_oracle().suggest_tip_cap().await.map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn fee_history( @@ -642,7 +642,7 @@ where .map_err(|e| e.to_string()) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn send_raw_transaction( @@ -672,7 +672,7 @@ where Ok(hash) }; - await_jh_option!(hctx.spawn_blocking_with_ctx(task)) + await_handler!(@option hctx.spawn_blocking_with_ctx(task)) } pub(super) async fn get_logs( @@ -686,7 +686,7 @@ where { let task = async move { ctx.signet().logs(&filter).await.map_err(EthError::into_string) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn new_filter( @@ -701,7 +701,7 @@ where let task = async move { ctx.signet().install_log_filter(filter).map_err(EthError::into_string) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn new_block_filter( @@ -714,7 +714,7 @@ where { let task = async move { ctx.signet().install_block_filter().map_err(EthError::into_string) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn uninstall_filter( @@ -728,7 +728,7 @@ where { let task = async move { Ok(ctx.signet().uninstall_filter(id)) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn get_filter_changes( @@ -742,7 +742,7 @@ where { let task = async move { ctx.signet().filter_changes(id).await.map_err(EthError::into_string) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } pub(super) async fn subscribe( @@ -763,7 +763,7 @@ where .ok_or_else(|| "pubsub not enabled".to_string()) }; - await_jh_option!(hctx.spawn_blocking_with_ctx(task)) + await_handler!(@option hctx.spawn_blocking_with_ctx(task)) } pub(super) async fn unsubscribe( @@ -777,5 +777,5 @@ where { let task = async move { Ok(ctx.signet().subscriptions().unsubscribe(id)) }; - await_jh_option!(hctx.spawn_blocking(task)) + await_handler!(@option hctx.spawn_blocking(task)) } diff --git a/crates/rpc/src/eth/error.rs b/crates/rpc/src/eth/error.rs index ef1b3a2..2d85cce 100644 --- a/crates/rpc/src/eth/error.rs +++ b/crates/rpc/src/eth/error.rs @@ -18,8 +18,8 @@ pub enum EthError { } impl EthError { - /// Turn into a string by value, allows for `.map_err(EthError::to_string)` - /// to be used. + /// Turn into a string by value, allows for + /// `.map_err(EthError::into_string)` to be used. pub fn into_string(self) -> String { ToString::to_string(&self) } diff --git a/crates/rpc/src/inspect/endpoints.rs b/crates/rpc/src/inspect/endpoints.rs index a65824a..f055fe6 100644 --- a/crates/rpc/src/inspect/endpoints.rs +++ b/crates/rpc/src/inspect/endpoints.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use crate::{ RpcCtx, inspect::db::{DbArgs, ListTableViewer}, - utils::{await_jh_option_response, response_tri}, + utils::{await_handler, response_tri}, }; use ajj::{HandlerCtx, ResponsePayload}; use reth::providers::providers::ProviderNodeTypes; @@ -37,5 +37,5 @@ where ResponsePayload::Success(output) }; - await_jh_option_response!(hctx.spawn_blocking(task)) + await_handler!(@response_option hctx.spawn_blocking(task)) } diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 174bdf1..d55c2c1 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -54,7 +54,10 @@ mod config; pub use config::{RpcServerGuard, ServeConfig}; mod ctx; -pub use ctx::{RpcCtx, RuRevmState, SignetCtx}; +pub use ctx::{LoadState, RpcCtx, RuRevmState, SignetCtx}; + +mod debug; +pub use debug::{DebugError, debug}; mod eth; pub use eth::{CallErrorData, EthError, eth}; @@ -88,7 +91,10 @@ where Host: FullNodeComponents, Signet: Pnt, { - ajj::Router::new().nest("eth", eth::()).nest("signet", signet::()) + ajj::Router::new() + .nest("eth", eth::()) + .nest("signet", signet::()) + .nest("debug", debug::()) } /// Create a new hazmat router that exposes the `inspect` API. diff --git a/crates/rpc/src/signet/endpoints.rs b/crates/rpc/src/signet/endpoints.rs index 4dc988b..b0921a5 100644 --- a/crates/rpc/src/signet/endpoints.rs +++ b/crates/rpc/src/signet/endpoints.rs @@ -1,7 +1,7 @@ use crate::{ ctx::RpcCtx, signet::error::SignetError, - utils::{await_jh_option, await_jh_option_response, response_tri}, + utils::{await_handler, response_tri}, }; use ajj::{HandlerCtx, ResponsePayload}; use reth_node_api::FullNodeComponents; @@ -32,7 +32,7 @@ where Ok(()) }; - await_jh_option!(hctx.spawn_blocking_with_ctx(task)) + await_handler!(@option hctx.spawn_blocking_with_ctx(task)) } pub(super) async fn send_order( @@ -54,7 +54,7 @@ where Ok(()) }; - await_jh_option!(hctx.spawn_blocking_with_ctx(task)) + await_handler!(@option hctx.spawn_blocking_with_ctx(task)) } pub(super) async fn call_bundle( @@ -102,5 +102,5 @@ where } }; - await_jh_option_response!(hctx.spawn_blocking(task)) + await_handler!(@response_option hctx.spawn_blocking(task)) } diff --git a/crates/rpc/src/utils.rs b/crates/rpc/src/utils.rs index 34a8ec1..a20d561 100644 --- a/crates/rpc/src/utils.rs +++ b/crates/rpc/src/utils.rs @@ -11,18 +11,33 @@ use tokio::task::JoinHandle; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; use tracing::error; -macro_rules! await_jh_option { +macro_rules! await_handler { ($h:expr) => { + match $h.await { + Ok(res) => res, + Err(_) => return Err("task panicked or cancelled".to_string()), + } + }; + + (@option $h:expr) => { match $h.await { Ok(Some(res)) => res, _ => return Err("task panicked or cancelled".to_string()), } }; -} -pub(crate) use await_jh_option; -macro_rules! await_jh_option_response { - ($h:expr) => { + (@response $h:expr) => { + match $h.await { + Ok(res) => res, + _ => { + return ResponsePayload::internal_error_message(std::borrow::Cow::Borrowed( + "task panicked or cancelled", + )) + } + } + }; + + (@response_option $h:expr) => { match $h.await { Ok(Some(res)) => res, _ => { @@ -33,7 +48,8 @@ macro_rules! await_jh_option_response { } }; } -pub(crate) use await_jh_option_response; + +pub(crate) use await_handler; macro_rules! response_tri { ($h:expr) => {