Skip to content
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
2 changes: 2 additions & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
131 changes: 113 additions & 18 deletions crates/rpc/src/ctx/full.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<BlockId> for LoadState {
fn from(value: BlockId) -> Self {
match value {
BlockId::Number(no) => no.into(),
_ => LoadState::After,
}
}
}

impl From<BlockNumberOrTag> for LoadState {
fn from(value: BlockNumberOrTag) -> Self {
match value {
BlockNumberOrTag::Pending => LoadState::Before,
_ => LoadState::After,
}
}
}

impl From<LoadState> for bool {
fn from(value: LoadState) -> Self {
matches!(value, LoadState::Before)
}
}

/// RPC context. Contains all necessary host and signet components for serving
/// RPC requests.
Expand Down Expand Up @@ -82,6 +138,12 @@ where
}
}

/// Shared context between all RPC handlers.
#[derive(Debug)]
struct SharedContext {
tracing_semaphores: Arc<Semaphore>,
}

/// Inner context for [`RpcCtx`].
#[derive(Debug)]
pub struct RpcCtxInner<Host, Signet>
Expand All @@ -91,6 +153,8 @@ where
{
host: Host,
signet: SignetCtx<Signet>,

shared: SharedContext,
}

impl<Host, Signet> RpcCtxInner<Host, Signet>
Expand Down Expand Up @@ -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<OwnedSemaphorePermit, AcquireError> {
self.shared.tracing_semaphores.clone().acquire_owned().await
}

pub const fn host(&self) -> &Host {
Expand All @@ -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<I: Inspector<Ctx<RuRevmState>>>(
&self,
block_id: BlockId,
block: &Header,
) -> Result<EvmNeedsTx<RuRevmState>, 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<EvmNeedsTx<RuRevmState, I>, 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<EvmNeedsTx<RuRevmState>, EthApiError> {
self.trevm_with_inspector(state, header, trevm::revm::inspector::NoOpInspector)
}
}

// Some code in this file has been copied and modified from reth
Expand Down
2 changes: 1 addition & 1 deletion crates/rpc/src/ctx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
170 changes: 170 additions & 0 deletions crates/rpc/src/debug/endpoints.rs
Original file line number Diff line number Diff line change
@@ -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>(T, #[serde(default)] Option<GethDebugTracingOptions>);

/// Params type for `debug_traceTransaction`.`
#[derive(Debug, serde::Deserialize)]
pub(super) struct TraceTransactionParams(B256, #[serde(default)] Option<GethDebugTracingOptions>);

/// `debug_traceBlockByNumber` and `debug_traceBlockByHash` endpoint handler.
pub(super) async fn trace_block<T, Host, Signet>(
hctx: HandlerCtx,
TraceBlockParams(id, opts): TraceBlockParams<T>,
ctx: RpcCtx<Host, Signet>,
) -> ResponsePayload<Vec<TraceResult>, DebugError>
where
T: Into<BlockId>,
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<Host, Signet>(
hctx: HandlerCtx,
TraceTransactionParams(tx_hash, opts): TraceTransactionParams,
ctx: RpcCtx<Host, Signet>,
) -> ResponsePayload<GethTrace, DebugError>
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))
}
Loading