-
Notifications
You must be signed in to change notification settings - Fork 207
feat(l1): implement noopTracer for debug trace endpoints #6694
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
Changes from 6 commits
7970f49
e68699c
778ffc9
218f834
ea80e96
28da6c4
1d70566
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,6 +59,11 @@ enum TracerType { | |
| /// `structLogger` wrapper shape (`{failed, gas, returnValue, structLogs}`). | ||
| /// Selected via `"tracer": "opcodeTracer"`. | ||
| OpcodeTracer, | ||
| /// No-op tracer that re-executes the transaction(s) through the EVM with no tracer | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: "matching geth's For the benchmarking goal this is arguably better (it isolates pure execution), but the doc could be precise so future readers don't assume the numbers are directly comparable to geth's noopTracer: /// No-op tracer: re-executes the transaction(s) through the EVM with no tracer
/// attached and returns empty JSON objects. The output shape matches geth's
/// `noopTracer` (`{}` per tx), but note the mechanism differs — geth runs an
/// empty-hook tracer (paying per-opcode dispatch), whereas this skips tracer
/// hooks entirely, isolating raw execution cost. Any `tracerConfig` is ignored.
/// Selected via `"tracer": "noopTracer"`.
Non-blocking — just so a future benchmark comparison isn't misread. |
||
| /// attached and returns empty JSON objects, matching geth's `noopTracer`. Useful for | ||
| /// benchmarking raw execution overhead without tracing cost. Any `tracerConfig` is | ||
| /// ignored. Selected via `"tracer": "noopTracer"`. | ||
| NoopTracer, | ||
| } | ||
|
|
||
| #[derive(Deserialize, Default)] | ||
|
|
@@ -209,6 +214,17 @@ impl RpcHandler for TraceTransactionRequest { | |
| emit, | ||
| })?) | ||
| } | ||
| TracerType::NoopTracer => { | ||
| // Like geth, noopTracer still executes the transaction (validating | ||
| // it exists and that parent state can be rebuilt) but with no tracer | ||
| // attached — so we pay only the raw execution cost, then return {}. | ||
| context | ||
| .blockchain | ||
| .noop_trace_transaction(self.tx_hash, reexec, timeout) | ||
| .await | ||
| .map_err(|err| RpcErr::Internal(err.to_string()))?; | ||
| Ok(serde_json::json!({})) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -355,6 +371,105 @@ impl RpcHandler for TraceBlockByNumberRequest { | |
| .collect::<Result<_, serde_json::Error>>()?; | ||
| Ok(serde_json::to_value(block_trace)?) | ||
| } | ||
| TracerType::NoopTracer => { | ||
| // Like geth, noopTracer re-executes every transaction in the block | ||
| // with no tracer attached, then emits {} per tx. | ||
| let tx_hashes = context | ||
| .blockchain | ||
| .noop_trace_block(block, reexec, timeout) | ||
| .await | ||
| .map_err(|err| RpcErr::Internal(err.to_string()))?; | ||
| let block_trace: BlockTrace<Value> = tx_hashes | ||
| .into_iter() | ||
| .map(|hash| (hash, serde_json::json!({})).into()) | ||
| .collect(); | ||
| Ok(serde_json::to_value(block_trace)?) | ||
| } | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use crate::rpc::RpcHandler; | ||
| use serde_json::json; | ||
|
|
||
| // --- TraceTransactionRequest parse tests --- | ||
|
|
||
| #[test] | ||
| fn parse_trace_tx_with_hash_only() { | ||
| let params = Some(vec![json!( | ||
| "0x0000000000000000000000000000000000000000000000000000000000000001" | ||
| )]); | ||
| let req = TraceTransactionRequest::parse(¶ms).unwrap(); | ||
| assert_eq!(req.tx_hash, H256::from_low_u64_be(1)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parse_trace_tx_with_config() { | ||
| let params = Some(vec![ | ||
| json!("0x0000000000000000000000000000000000000000000000000000000000000001"), | ||
| json!({"tracer": "callTracer", "tracerConfig": {"onlyTopCall": true}}), | ||
| ]); | ||
| let req = TraceTransactionRequest::parse(¶ms).unwrap(); | ||
| assert!(matches!(req.trace_config.tracer, TracerType::CallTracer)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parse_trace_tx_no_params() { | ||
| assert!(TraceTransactionRequest::parse(&None).is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parse_trace_tx_too_many_params() { | ||
| let params = Some(vec![json!("0x01"), json!({}), json!("extra")]); | ||
| assert!(TraceTransactionRequest::parse(¶ms).is_err()); | ||
| } | ||
|
|
||
| // --- TracerType deserialization tests --- | ||
|
|
||
| #[test] | ||
| fn deserialize_tracer_type_noop() { | ||
| let t: TracerType = serde_json::from_value(json!("noopTracer")).unwrap(); | ||
| assert!(matches!(t, TracerType::NoopTracer)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn deserialize_tracer_type_unknown_fails() { | ||
| assert!(serde_json::from_value::<TracerType>(json!("unknownTracer")).is_err()); | ||
| } | ||
|
|
||
| // --- TraceConfig deserialization tests --- | ||
|
|
||
| #[test] | ||
| fn deserialize_trace_config_defaults() { | ||
| let cfg: TraceConfig = serde_json::from_value(json!({})).unwrap(); | ||
| assert!(matches!(cfg.tracer, TracerType::CallTracer)); | ||
| assert!(cfg.timeout.is_none()); | ||
| assert!(cfg.reexec.is_none()); | ||
| } | ||
|
|
||
| // --- PrestateTracerConfig validation tests --- | ||
|
|
||
| #[test] | ||
| fn prestate_config_diff_mode_and_include_empty_is_invalid() { | ||
| let cfg = PrestateTracerConfig { | ||
| diff_mode: true, | ||
| include_empty: true, | ||
| }; | ||
| assert!(cfg.validate().is_err()); | ||
| } | ||
|
|
||
| // --- noopTracer parse test --- | ||
|
|
||
| #[test] | ||
| fn parse_trace_tx_noop_tracer() { | ||
| let params = Some(vec![ | ||
| json!("0x0000000000000000000000000000000000000000000000000000000000000001"), | ||
| json!({"tracer": "noopTracer"}), | ||
| ]); | ||
| let req = TraceTransactionRequest::parse(¶ms).unwrap(); | ||
| assert!(matches!(req.trace_config.tracer, TracerType::NoopTracer)); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional consistency port from the duplicate #6657. Here the target tx is folded into the replay by stopping at
tx_index + 1. The sibling tracers (trace_tx_prestate,trace_tx_opcodes) instead replay[0, tx_index)and then run the target tx through a dedicated step — and #6657's noop path follows that same shape (rerun_block(Some(tx_index))+ a separatetrace_tx_noop(target)).For a noop the result is identical either way, so this is purely about keeping the module uniform:
Non-blocking; adopt only if you want parity with the prestate/opcode paths. The current
Some(tx_index + 1)form is correct as-is.