What is missing
Besu's debug_trace* methods support the opcode logger and a fixed set of named tracers (callTracer, prestateTracer, 4byteTracer), but there is no way to run a tracer that a user writes themselves. Geth has supported this for years. You pass a small JavaScript object with step, result, fault, and optionally setup, enter, and exit, and the node runs it against the transaction's EVM execution. Whatever result returns becomes the response.
Reference: https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer
Proposal
Add Geth-compatible custom JavaScript tracer support so the same JavaScript tracer that runs on Geth runs unchanged on Besu. The engine would be GraalJS (org.graalvm.polyglot), which runs on the JDK Besu already uses, similar to how Geth uses goja.
Why it helps
A lot of debugging, analytics, and security tooling ships its own JavaScript tracer and assumes a Geth-style node, so people relying on it have to keep a Geth node around. This closes that gap and lets users express new trace logic at request time without waiting for a new Besu release. It builds on request fields and the EVM hook interface Besu already has, so it is additive rather than a new subsystem.
Approach, validated with a short spike on current main
The existing named tracers are post-processing converters that run after execution on a recorded trace. A JavaScript tracer needs to run live, on every opcode, with its db object reflecting state at that step. So it would attach as a live OperationTracer, mapping tracePreExecution to step, traceEndTransaction to result, and the context enter and exit hooks to enter and exit. MessageFrame already exposes what the Geth log and db objects need, including live world state.
I ran a spike on current main. The affected modules (evm, ethereum/api) compile cleanly with a minimal JsOperationTracer wired in. The spike also confirmed the gates to plan for:
- Dependency verification (
gradle/verification-metadata.xml) needs regenerated checksums for the GraalJS artifacts.
- The GraalVM license needs adding to
gradle/allowed-licenses.json.
- GraalJS is interpreted on a standard JDK, so it is slower than the native tracers. The feature is opt-in and off any hot path.
- Native image needs reachability metadata, so the standard build is the initial target.
Scope
Wire it into debug_traceTransaction first, then extend to the other debug_trace* methods. Include execution timeouts and resource limits so a tracer cannot hang the node, and tests that run real unmodified Geth tracers and compare the output.
Would this be welcome as a contribution, and is there a preferred direction on the live tracer routing? Happy to take it on in small, reviewable phases.
cc @usmansaleem @Gabriel-Trintinalia @jflo
Proposal-js-tracer.pdf
What is missing
Besu's
debug_trace*methods support the opcode logger and a fixed set of named tracers (callTracer,prestateTracer,4byteTracer), but there is no way to run a tracer that a user writes themselves. Geth has supported this for years. You pass a small JavaScript object withstep,result,fault, and optionallysetup,enter, andexit, and the node runs it against the transaction's EVM execution. Whateverresultreturns becomes the response.Reference: https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer
Proposal
Add Geth-compatible custom JavaScript tracer support so the same JavaScript tracer that runs on Geth runs unchanged on Besu. The engine would be GraalJS (
org.graalvm.polyglot), which runs on the JDK Besu already uses, similar to how Geth uses goja.Why it helps
A lot of debugging, analytics, and security tooling ships its own JavaScript tracer and assumes a Geth-style node, so people relying on it have to keep a Geth node around. This closes that gap and lets users express new trace logic at request time without waiting for a new Besu release. It builds on request fields and the EVM hook interface Besu already has, so it is additive rather than a new subsystem.
Approach, validated with a short spike on current main
The existing named tracers are post-processing converters that run after execution on a recorded trace. A JavaScript tracer needs to run live, on every opcode, with its
dbobject reflecting state at that step. So it would attach as a liveOperationTracer, mappingtracePreExecutiontostep,traceEndTransactiontoresult, and the context enter and exit hooks toenterandexit.MessageFramealready exposes what the Gethloganddbobjects need, including live world state.I ran a spike on current
main. The affected modules (evm,ethereum/api) compile cleanly with a minimalJsOperationTracerwired in. The spike also confirmed the gates to plan for:gradle/verification-metadata.xml) needs regenerated checksums for the GraalJS artifacts.gradle/allowed-licenses.json.Scope
Wire it into
debug_traceTransactionfirst, then extend to the otherdebug_trace*methods. Include execution timeouts and resource limits so a tracer cannot hang the node, and tests that run real unmodified Geth tracers and compare the output.Would this be welcome as a contribution, and is there a preferred direction on the live tracer routing? Happy to take it on in small, reviewable phases.
cc @usmansaleem @Gabriel-Trintinalia @jflo
Proposal-js-tracer.pdf