Skip to content

Commit 84f6430

Browse files
authored
high-value debug_ namespace (#18)
* wip * fix: make the handler compile * wip * wip * feat: both endpoints * lint: clippy * fix: enable the router * chore: basic tracing * feat: tracing permits
1 parent f643a42 commit 84f6430

File tree

13 files changed

+677
-65
lines changed

13 files changed

+677
-65
lines changed

crates/rpc/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ tower-http = { version = "0.6.2", features = ["cors"] }
4141
tracing.workspace = true
4242
serde_json.workspace = true
4343
futures-util = "0.3.31"
44+
itertools.workspace = true
45+
revm-inspectors = "0.26.5"
4446

4547
[dev-dependencies]
4648
signet-zenith.workspace = true

crates/rpc/src/ctx/full.rs

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use crate::{RuRevmState, SignetCtx};
2-
use alloy::{
3-
consensus::{BlockHeader, Header},
4-
eips::BlockId,
5-
};
2+
use alloy::{consensus::Header, eips::BlockId};
63
use reth::{
74
providers::{ProviderFactory, ProviderResult, providers::BlockchainProvider},
85
rpc::server_types::eth::{EthApiError, EthConfig},
6+
rpc::types::BlockNumberOrTag,
97
tasks::{TaskExecutor, TaskSpawner},
108
};
119
use reth_node_api::FullNodeComponents;
@@ -14,6 +12,64 @@ use signet_node_types::Pnt;
1412
use signet_tx_cache::client::TxCache;
1513
use signet_types::constants::SignetSystemConstants;
1614
use std::sync::Arc;
15+
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore};
16+
use trevm::{helpers::Ctx, revm::Inspector};
17+
18+
/// State location when instantiating an EVM instance.
19+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20+
#[repr(i8)]
21+
pub enum LoadState {
22+
/// Load the state before the block's transactions (i.e. at the start of
23+
/// the block).
24+
Before = -1,
25+
/// Load the state after the block's transactions (i.e. at the end of the
26+
/// block).
27+
After = 0,
28+
}
29+
30+
impl LoadState {
31+
/// Adjust the height based on the state location.
32+
pub const fn adjust_height(&self, height: u64) -> u64 {
33+
match self {
34+
LoadState::Before => height.saturating_sub(1),
35+
LoadState::After => height,
36+
}
37+
}
38+
39+
/// Returns `true` if the state location is before the block.
40+
pub const fn is_before_block(&self) -> bool {
41+
matches!(self, Self::Before)
42+
}
43+
44+
/// Returns `true` if the state location is after the block.
45+
pub const fn is_after_block(&self) -> bool {
46+
matches!(self, Self::After)
47+
}
48+
}
49+
50+
impl From<BlockId> for LoadState {
51+
fn from(value: BlockId) -> Self {
52+
match value {
53+
BlockId::Number(no) => no.into(),
54+
_ => LoadState::After,
55+
}
56+
}
57+
}
58+
59+
impl From<BlockNumberOrTag> for LoadState {
60+
fn from(value: BlockNumberOrTag) -> Self {
61+
match value {
62+
BlockNumberOrTag::Pending => LoadState::Before,
63+
_ => LoadState::After,
64+
}
65+
}
66+
}
67+
68+
impl From<LoadState> for bool {
69+
fn from(value: LoadState) -> Self {
70+
matches!(value, LoadState::Before)
71+
}
72+
}
1773

1874
/// RPC context. Contains all necessary host and signet components for serving
1975
/// RPC requests.
@@ -82,6 +138,12 @@ where
82138
}
83139
}
84140

141+
/// Shared context between all RPC handlers.
142+
#[derive(Debug)]
143+
struct SharedContext {
144+
tracing_semaphores: Arc<Semaphore>,
145+
}
146+
85147
/// Inner context for [`RpcCtx`].
86148
#[derive(Debug)]
87149
pub struct RpcCtxInner<Host, Signet>
@@ -91,6 +153,8 @@ where
91153
{
92154
host: Host,
93155
signet: SignetCtx<Signet>,
156+
157+
shared: SharedContext,
94158
}
95159

96160
impl<Host, Signet> RpcCtxInner<Host, Signet>
@@ -122,8 +186,20 @@ where
122186
where
123187
Tasks: TaskSpawner + Clone + 'static,
124188
{
125-
SignetCtx::new(constants, factory, provider, eth_config, tx_cache, spawner)
126-
.map(|signet| Self { host, signet })
189+
SignetCtx::new(constants, factory, provider, eth_config, tx_cache, spawner).map(|signet| {
190+
Self {
191+
host,
192+
signet,
193+
shared: SharedContext {
194+
tracing_semaphores: Semaphore::new(eth_config.max_tracing_requests).into(),
195+
},
196+
}
197+
})
198+
}
199+
200+
/// Acquire a permit for tracing.
201+
pub async fn acquire_tracing_permit(&self) -> Result<OwnedSemaphorePermit, AcquireError> {
202+
self.shared.tracing_semaphores.clone().acquire_owned().await
127203
}
128204

129205
pub const fn host(&self) -> &Host {
@@ -138,26 +214,45 @@ where
138214
self.host.task_executor()
139215
}
140216

141-
/// Create a trevm instance.
142-
pub fn trevm(
217+
/// Instantiate a trevm instance with a custom inspector.
218+
///
219+
/// The `header` argument is used to fill the block context of the EVM. If
220+
/// the `block_id` is `Pending` the EVM state will be the block BEFORE the
221+
/// `header`. I.e. if the block number of the `header` is `n`, the state
222+
/// will be after block `n-1`, (effectively the state at the start of block
223+
/// `n`).
224+
///
225+
/// if the `block_id` is `Pending` the state will be based on the
226+
/// and `block` arguments
227+
pub fn trevm_with_inspector<I: Inspector<Ctx<RuRevmState>>>(
143228
&self,
144-
block_id: BlockId,
145-
block: &Header,
146-
) -> Result<EvmNeedsTx<RuRevmState>, EthApiError> {
147-
// decrement if the id is pending, so that the state is on the latest block
148-
let height = block.number() - block_id.is_pending() as u64;
149-
let spec_id = self.signet.evm_spec_id(block);
229+
state: LoadState,
230+
header: &Header,
231+
inspector: I,
232+
) -> Result<EvmNeedsTx<RuRevmState, I>, EthApiError> {
233+
let load_height = state.adjust_height(header.number);
234+
let spec_id = self.signet.evm_spec_id(header);
150235

151-
let db = self.signet.state_provider_database(height)?;
236+
let db = self.signet.state_provider_database(load_height)?;
152237

153-
let mut trevm = signet_evm::signet_evm(db, self.signet.constants().clone())
154-
.fill_cfg(&self.signet)
155-
.fill_block(block);
238+
let mut trevm =
239+
signet_evm::signet_evm_with_inspector(db, inspector, self.signet.constants().clone())
240+
.fill_cfg(&self.signet)
241+
.fill_block(header);
156242

157243
trevm.set_spec_id(spec_id);
158244

159245
Ok(trevm)
160246
}
247+
248+
/// Create a trevm instance.
249+
pub fn trevm(
250+
&self,
251+
state: LoadState,
252+
header: &Header,
253+
) -> Result<EvmNeedsTx<RuRevmState>, EthApiError> {
254+
self.trevm_with_inspector(state, header, trevm::revm::inspector::NoOpInspector)
255+
}
161256
}
162257

163258
// Some code in this file has been copied and modified from reth

crates/rpc/src/ctx/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod signet;
22
pub use signet::SignetCtx;
33

44
mod full;
5-
pub use full::RpcCtx;
5+
pub use full::{LoadState, RpcCtx};
66

77
mod fee_hist;
88
pub(crate) use fee_hist::strip_signet_system_txns;

crates/rpc/src/debug/endpoints.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use crate::{
2+
DebugError, RpcCtx,
3+
utils::{await_handler, response_tri},
4+
};
5+
use ajj::{HandlerCtx, ResponsePayload};
6+
use alloy::{consensus::BlockHeader, eips::BlockId, primitives::B256};
7+
use itertools::Itertools;
8+
use reth::rpc::{
9+
server_types::eth::EthApiError,
10+
types::{
11+
TransactionInfo,
12+
trace::geth::{GethDebugTracingOptions, GethTrace, TraceResult},
13+
},
14+
};
15+
use reth_node_api::FullNodeComponents;
16+
use signet_evm::EvmErrored;
17+
use signet_node_types::Pnt;
18+
use signet_types::MagicSig;
19+
use tracing::Instrument;
20+
21+
/// Params for the `debug_traceBlockByNumber` and `debug_traceBlockByHash`
22+
/// endpoints.
23+
#[derive(Debug, serde::Deserialize)]
24+
pub(super) struct TraceBlockParams<T>(T, #[serde(default)] Option<GethDebugTracingOptions>);
25+
26+
/// Params type for `debug_traceTransaction`.`
27+
#[derive(Debug, serde::Deserialize)]
28+
pub(super) struct TraceTransactionParams(B256, #[serde(default)] Option<GethDebugTracingOptions>);
29+
30+
/// `debug_traceBlockByNumber` and `debug_traceBlockByHash` endpoint handler.
31+
pub(super) async fn trace_block<T, Host, Signet>(
32+
hctx: HandlerCtx,
33+
TraceBlockParams(id, opts): TraceBlockParams<T>,
34+
ctx: RpcCtx<Host, Signet>,
35+
) -> ResponsePayload<Vec<TraceResult>, DebugError>
36+
where
37+
T: Into<BlockId>,
38+
Host: FullNodeComponents,
39+
Signet: Pnt,
40+
{
41+
let _permit = response_tri!(
42+
ctx.acquire_tracing_permit()
43+
.await
44+
.map_err(|_| DebugError::rpc_error("Failed to acquire tracing permit".into()))
45+
);
46+
47+
let id = id.into();
48+
let span = tracing::debug_span!("traceBlock", ?id, tracer = ?opts.as_ref().and_then(|o| o.tracer.as_ref()));
49+
50+
let fut = async move {
51+
// Fetch the block by ID
52+
let Some((hash, block)) = response_tri!(ctx.signet().raw_block(id).await) else {
53+
return ResponsePayload::internal_error_message(
54+
EthApiError::HeaderNotFound(id).to_string().into(),
55+
);
56+
};
57+
58+
tracing::debug!(number = block.number(), "Loaded block");
59+
60+
// Allocate space for the frames
61+
let mut frames = Vec::with_capacity(block.transaction_count());
62+
63+
// Instantiate the EVM with the block
64+
let mut trevm = response_tri!(ctx.trevm(crate::LoadState::Before, block.header()));
65+
66+
// Apply all transactions in the block up, tracing each one
67+
let opts = opts.unwrap_or_default();
68+
69+
tracing::trace!(?opts, "Tracing block transactions");
70+
71+
let mut txns = block.body().transactions().enumerate().peekable();
72+
for (idx, tx) in txns
73+
.by_ref()
74+
.peeking_take_while(|(_, t)| MagicSig::try_from_signature(t.signature()).is_none())
75+
{
76+
let tx_info = TransactionInfo {
77+
hash: Some(*tx.hash()),
78+
index: Some(idx as u64),
79+
block_hash: Some(hash),
80+
block_number: Some(block.header().number()),
81+
base_fee: block.header().base_fee_per_gas(),
82+
};
83+
84+
let t = trevm.fill_tx(tx);
85+
86+
let frame;
87+
(frame, trevm) = response_tri!(crate::debug::tracer::trace(t, &opts, tx_info));
88+
frames.push(TraceResult::Success { result: frame, tx_hash: Some(*tx.hash()) });
89+
90+
tracing::debug!(tx_index = idx, tx_hash = ?tx.hash(), "Traced transaction");
91+
}
92+
93+
ResponsePayload::Success(frames)
94+
}
95+
.instrument(span);
96+
97+
await_handler!(@response_option hctx.spawn_blocking(fut))
98+
}
99+
100+
/// Handle for `debug_traceTransaction`.
101+
pub(super) async fn trace_transaction<Host, Signet>(
102+
hctx: HandlerCtx,
103+
TraceTransactionParams(tx_hash, opts): TraceTransactionParams,
104+
ctx: RpcCtx<Host, Signet>,
105+
) -> ResponsePayload<GethTrace, DebugError>
106+
where
107+
Host: FullNodeComponents,
108+
Signet: Pnt,
109+
{
110+
let _permit = response_tri!(
111+
ctx.acquire_tracing_permit()
112+
.await
113+
.map_err(|_| DebugError::rpc_error("Failed to acquire tracing permit".into()))
114+
);
115+
116+
let span = tracing::debug_span!("traceTransaction", %tx_hash, tracer = ?opts.as_ref().and_then(|o| o.tracer.as_ref()));
117+
118+
let fut = async move {
119+
// Load the transaction by hash
120+
let (tx, meta) = response_tri!(
121+
response_tri!(ctx.signet().raw_transaction_by_hash(tx_hash))
122+
.ok_or(EthApiError::TransactionNotFound)
123+
);
124+
125+
tracing::debug!("Loaded transaction metadata");
126+
127+
// Load the block containing the transaction
128+
let res = response_tri!(ctx.signet().raw_block(meta.block_hash).await);
129+
let (_, block) =
130+
response_tri!(res.ok_or_else(|| EthApiError::HeaderNotFound(meta.block_hash.into())));
131+
132+
tracing::debug!(number = block.number(), "Loaded containing block");
133+
134+
// Load trevm at the start of the block (i.e. before any transactions are applied)
135+
let mut trevm = response_tri!(ctx.trevm(crate::LoadState::Before, block.header()));
136+
137+
// Apply all transactions in the block up to (but not including) the
138+
// target one
139+
let mut txns = block.body().transactions().enumerate().peekable();
140+
for (_idx, tx) in txns.by_ref().peeking_take_while(|(_, t)| t.hash() != tx.hash()) {
141+
if MagicSig::try_from_signature(tx.signature()).is_some() {
142+
return ResponsePayload::internal_error_message(
143+
EthApiError::TransactionNotFound.to_string().into(),
144+
);
145+
}
146+
147+
trevm = response_tri!(trevm.run_tx(tx).map_err(EvmErrored::into_error)).accept_state();
148+
}
149+
150+
let (index, tx) = response_tri!(txns.next().ok_or(EthApiError::TransactionNotFound));
151+
152+
let trevm = trevm.fill_tx(tx);
153+
154+
let tx_info = TransactionInfo {
155+
hash: Some(*tx.hash()),
156+
index: Some(index as u64),
157+
block_hash: Some(block.hash()),
158+
block_number: Some(block.header().number()),
159+
base_fee: block.header().base_fee_per_gas(),
160+
};
161+
162+
let res =
163+
response_tri!(crate::debug::tracer::trace(trevm, &opts.unwrap_or_default(), tx_info)).0;
164+
165+
ResponsePayload::Success(res)
166+
}
167+
.instrument(span);
168+
169+
await_handler!(@response_option hctx.spawn_blocking(fut))
170+
}

0 commit comments

Comments
 (0)