Standardize opcode tracer behavior for debug_traceBlockByNumber and debug_traceTransaction#762
Standardize opcode tracer behavior for debug_traceBlockByNumber and debug_traceTransaction#762MysticRyuujin wants to merge 9 commits intoethereum:mainfrom
Conversation
| minimum: 0 | ||
| gasCost: | ||
| title: gas cost | ||
| description: The gas cost charged for executing this opcode. |
There was a problem hiding this comment.
This is not well specified. Does it mean the amount of gas to be burned by successful execution of the instruction?
There was a problem hiding this comment.
I updated the description to reflect the intent, though I'm not 100% sure if this wording is correct? I think it is.
There was a problem hiding this comment.
In geth, OnOpcode is called before the opcode executes, receiving the pre-computed cost as a parameter. gasCost is that value — the amount that will be deducted from the remaining gas assuming successful execution, including dynamic costs such as memory expansion and cold/warm storage access charges (EIP-2929). So yeah, I think the updated description is accurate.
There was a problem hiding this comment.
I think the main issue is in CALL instructions. I think gasCost should exclude the amount of gas passed down to the callee and only report the amount being burned as the instruction cost. This way the sum of all gasCost entries should give you the final gas used amount. cc @MariusVanDerWijden.
There was a problem hiding this comment.
Idea about sum being equal to the final used amount looks interesting, but want to highlight a few points:
- For formula to hold CALL
gasCostwon't include any "execution" gas, as it will already be accounted for in "inner" opcodes. Only base call gas, memory expansion, value transfer, and account creation fees. This may be a bit counterintuitive. - From a brief check none of the top clients have such exact implementation, so this will require a change from all of them. Also most clients take full forwarded gas as
gasCost(without deducting amount returned by the callee). May be wrong here so.
+1 from Erigon |
b2f7439 to
4805dab
Compare
macfarla
left a comment
There was a problem hiding this comment.
the execution timeout is non-trivial to implement in Besu. would prefer this to be a nice to have rather than a must-have. everything else is fine.
| each StructLog entry. Ignored when tracer is set. | ||
| Default: false. | ||
| type: boolean | ||
| limit: |
There was a problem hiding this comment.
medium effort to implement this in Besu since we don't have this currently
| $ref: '#/components/schemas/TraceConfig' | ||
| errors: | ||
| - code: 4444 | ||
| message: Pruned history unavailable |
There was a problem hiding this comment.
probably also an error for "block not found" and parent not found
This is a breaking change in the opcode (structLog) tracer. Several fields will have a slight formatting difference to conform to the newly established spec at: ethereum/execution-apis#762. The differences include: - `memory`: words will have the 0x prefix. Also last word of memory will be padded to 32-bytes. - `storage`: keys and values will have the 0x prefix. --------- Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
|
go-ethereum PR was merged but there is no release, go.mod needs to be updated |
| description: >- | ||
| The contents of EVM memory at this step, divided into 32-byte chunks. | ||
| Each element is a 0x-prefixed, zero-padded 32-byte hex word representing | ||
| a consecutive 32-byte memory slot starting at offset (index * 32). |
There was a problem hiding this comment.
When stack, storage and memory is enabled, we've seen block traces on mainnet which were bigger than 47GB and that was even without padding. I think we should try to reduce the trace size as much as possible and not do any padding
There was a problem hiding this comment.
Isn't the padding in practice only for the last word, which may be partial? The overhead seems low to me.
| The contents of EVM memory at this step, divided into 32-byte chunks. | ||
| Each element is a 0x-prefixed, zero-padded 32-byte hex word representing | ||
| a consecutive 32-byte memory slot starting at offset (index * 32). | ||
| This field is absent (not null) when enableMemory is false. |
There was a problem hiding this comment.
Similar to storage we should only populate this for op codes that touch memory to reduce the trace size
There was a problem hiding this comment.
Looking through the Besu codebase the following op codes touch memory:
MLOAD, KECCAK256, LOG0–LOG4, RETURN, REVERT, MSIZE, MSTORE, MSTORE8, CALLDATACOPY, CODECOPY, EXTCODECOPY, RETURNDATACOPY, CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE, CREATE2, MCOPY
There was a problem hiding this comment.
We discussed a similar idea which didn't end up happening because streaming traces alleviated some of the issue.
It is the same in spirit as to what you're suggesting with the difference that we focus on opcodes that change the memory (and anytime you enter a new scope) and at that point output the full memory.
There was a problem hiding this comment.
I just run tests on debug_traceBlockByNumber with memory tracing enabled, on mainnet, and saw a block that generated 643 GiB of json output :
0x17AEF7D 658854.28MB
this is just to show case that we need to optimize memory tracing, and to support @daniellehrner's idea.
There was a problem hiding this comment.
Maybe it's not worth to standardize this part then.
There was a problem hiding this comment.
I think it makes even more sense to standardize it to be sure that debug_trace* calls generate reasonable amount of data. So basically, we need to reduce the impact of memory tracing on the json output size.
There was a problem hiding this comment.
I'd like to point out that: 1) it is disabled by default and won't bloat the trace, 2) there are few use-cases that require memory.
That said, I fully support optimizing the impact of memory, either in this or a future PR. I'd propose a more granular flag than enableMemory for it. An enum which can support a range of values like none, full, onChange. Because there are multiple ways to optimise it, you can even go as far as only outputting the diff compared to last change.
There is a way to do this in a backwards compatible way too. If we pick full as the default value, then when enableMemory is toggled it behaves the same way it does today.
There was a problem hiding this comment.
One could optimize the onChange even further by only doing this on operations which cannot be deduced from the already known output of an evm trace with only the stack enabled. By re-execution one can now deduce how the memory looks like. The only exception here is if EXTCODECOPY/CODECOPY is being used, which requires access to the code.
Maybe I missed an edge case but I think for all other opcodes this can directly be deduced by re-execution using the known stack values. Also RETURNDATACOPY for precompiles would be able to be recalculated because we can infer the input, and therefore also the output.
I think that in current EVM the memory trace only has to consist of the code bytes accessed during the trace. This only has to be stored once. We only need the copied bytes and not the remainder of the code, so we do not have to include the full code for every EXTCODECOPY/CODECOPY hit (how to format these partial codes is a different question, if one copies all odd bytes of a contract this would likely lead to a lot of overhead bytes for the encoding of this).
This would obviously need tooling in order to help reconstruct the actual memory layouts (I think reusing the own EVM implementation works best here), but it would massively decrease the memory trace output. If the trace provides every state necessary for the trace to be re-run we are done. For txs as these do not have more than 16.7 / 2 ** 24 gas limit this is also a manageable size for each transaction (and one can calculate the upper limit of certain entries to the trace, like the amount of addresses with code, or the amount of storage slots)
Summary of changes for compliance (ethereum/execution-apis PR 762) see ethereum/execution-apis#762) The changes fix the EVM tracer JSON output (structLog) to align with the Ethereum JSON-RPC specification. --- 1. Last memory word truncation Before: the loop used i+32 <= len(memData), silently dropping the last chunk when memory size was not an exact multiple of 32 bytes. After: the loop iterates over all bytes; the last partial chunk is zero-padded to 32 bytes, as required by the spec (each memory word is always 32 bytes). --- 2. Missing 0x prefix on storage and memory Before: storage keys/values and memory words were serialized as raw hex (e.g. "0000...0001"). After: all hex values are emitted with the 0x prefix (e.g. "0x0000...0001"), as required by the Ethereum JSON-RPC specification. --- 3. error field always present even without errors Before: the "error" field was serialized as "error": "" even when no error occurred. After: with omitempty the field is omitted entirely when there is no error, making the output cleaner and consistent with the behavior expected by clients and Hive tests. --- Impacted APIs * debug_traceTransaction * debug_traceBlockByNumber * debug_traceBlockByHash * debug_traceCall * debug_traceCallMany All three fixes are covered by unit tests in the new json_stream_test.go file. Out of this PR is keyword to anable/disable and default value for them ### Tracer Configuration Comparison | Field | Erigon | Geth | Spec #762 | Aligned | | :--- | :--- | :--- | :--- | :---: | | **Memory** | `disable=false` (ON) | `Enable=false` (OFF) | `enable=false` (OFF) | ❌ | | **Stack** | `disable=false` (ON) | `Disable=false` (ON) | `disable=false` (ON) | ✅ | | **Storage** | `disable=false` (ON) | `Disable=false` (ON) | `disable=false` (ON) | ✅ | | **Return** | `disable=false` (ON) | `Enable=false` (OFF) | `enable=false` (OFF) | ❌ | --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…nd debug_traceTransaction Made-with: Cursor
Made-with: Cursor
The reexec field is an implementation detail of geth's state reconstruction and does not belong in the standardized trace configuration.
Update the replace directive to use the fix-legacy-struct-log-json branch commit (c787778ed44a) from the MysticRyuujin fork, which fixes legacy structLog JSON encoding in the struct logger tracer.
…pdate go-ethereum - Add debug_traceBlockByHash method spec (mirrors debug_traceBlockByNumber but takes a block hash; clients MUST error when block not found) - Add timeout error-on-timeout behavior to TraceConfig and method descriptions - Update OpcodeBlockTransactionTrace description to reference both block trace methods - Add DebugTraceBlockByHash test generator (trace-block-with-transactions, trace-genesis, trace-block-not-found) - Update tools/go.mod replace to MysticRyuujin fork commit 957e1ed413ed (includes gofmt fix for formatMemoryWord) - Regenerate tests/debug_traceBlockByHash/ via make fill Made-with: Cursor
Clients that do not support execution timeouts (e.g. streaming JSON-RPC implementations) MAY ignore the timeout field. When a timeout is supported and reached, the client MUST still return an error with no partial results. Made-with: Cursor
…uctLog The opcode tracer spec (ethereum/execution-apis#762) requires storage keys and values to be 0x-prefixed bytes32. The serializer was explicitly stripping the prefix with [2..].
Two fixes to align with the [opcode tracer spec](ethereum/execution-apis#762): 1. Memory chunks now use `0x`-prefixed `bytes32` encoding. Previously `convert_memory()` used bare `hex::encode` without the prefix. 2. `returnData` is now included on every step when `enableReturnData` is true, even when the buffer is empty. Previously skipped when empty. --------- Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Drop MysticRyuujin go-ethereum replace; upstream v1.17.3 now carries the tracer fix. Resolve go.sum conflict and adopt main's updated testing_buildBlockV1 fixtures.
adc3ff5 to
a87ff44
Compare
Summary of changes for compliance (ethereum/execution-apis PR 762) see ethereum/execution-apis#762) Impacted APIs * debug_traceTransaction * debug_traceBlockByNumber * debug_traceBlockByHash * debug_traceCall * debug_traceCallMany --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace disableMemory/disableReturnData (opt-out, default ON) with enableMemory/enableReturnData (opt-in, default OFF) to match the ethereum/execution-apis#762 spec and Geth behavior. Also adds returnData emission to JsonStreamLogger which was previously missing. evm tool flags (--nomemory/--noreturndata) preserve existing behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace disableMemory/disableReturnData (opt-out, default ON) with enableMemory/enableReturnData (opt-in, default OFF) to match the ethereum/execution-apis#762 spec and Geth behavior. Also adds returnData emission to JsonStreamLogger which was previously missing. evm tool flags (--nomemory/--noreturndata) preserve existing behavior. needs Rpc-test tag --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…erigontech#20755) Replace disableMemory/disableReturnData (opt-out, default ON) with enableMemory/enableReturnData (opt-in, default OFF) to match the ethereum/execution-apis#762 spec and Geth behavior. Also adds returnData emission to JsonStreamLogger which was previously missing. evm tool flags (--nomemory/--noreturndata) preserve existing behavior. needs Rpc-test tag --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#9) * chore: bump revm 30.0.2 (paradigmxyz#379) bump patch * chore: bump revm 30.0.2 (paradigmxyz#380) * chore: bump to revm 33 (paradigmxyz#381) ## Motivation <!-- Explain the context and why you're making that change. What is the problem you're trying to solve? In some cases there is not a problem and this can be thought of as being the motivation for your change. --> Per: https://github.com/bluealloy/revm/releases/tag/v100 * chore: release 0.33.0 * feat: Fix geth prestate filtering for prefunded creations (paradigmxyz#383) This aims at resolving issues mentioned in paradigmxyz#382 , typically this one : <img width="1818" height="694" alt="image" src="https://github.com/user-attachments/assets/51b82944-c711-421b-8479-05095ae9bbce" /> To fix this , what i did was : - Keep track of addresses that are marked as Create and were truly empty in the database snapshot. Only these addresses are filtered from the prestate diff so prefunded contract deployments remain visible. - Introduce account_was_empty helper to avoid re-computing the emptiness check and add a regression test (prestate_diff_keeps_prefunded_created_accounts) that covers a prefunded creation vs. a true empty-address creation. cc @mattsse Please check it out if it resolves should close this one paradigmxyz/reth#19703 --------- Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> * feat: upstream DebugInspector from reth (paradigmxyz#384) Moves `DebugInspector` from reth to revm-inspectors as requested in paradigmxyz/reth#19932. This was introduced in paradigmxyz/reth#19925 to unify debug tracing across all Geth tracer types. * chore: release 0.33.1 * test: add test for string error decode (paradigmxyz#386) * fix: use pre code if hash matches (paradigmxyz#387) this fixes an issue where we would return diffs like ``` "post": { "0x093f6c270ac22ec240f0c6fd7414ea774ca8d3e5": {}, "0x2641c2ded63a0c640629f5edf1189e0f53c06561": {}, ``` for `0x91b066df39661db7bcca1cb8bb8afd11816414408d44cbfcf6144f440d5dfe3b` ref paradigmxyz/reth#19703 however nothing changed in the account here and these should have been filtered out via: https://github.com/paradigmxyz/revm-inspectors/blob/f7503520888efc11eac3142fe7d611c519811b25/src/tracing/builder/geth.rs#L349-L349 and cleared from post via: https://github.com/alloy-rs/alloy/blob/35e20437670d987680a6715bcc173f3d357f8254/crates/rpc-types-trace/src/geth/pre_state.rs#L188-L201 the reason why this didn't work is because: the code can be None here which would make retain_change not remove the values ``` "result": { "post": { "0x093f6c270ac22ec240f0c6fd7414ea774ca8d3e5": {}, "0x2641c2ded63a0c640629f5edf1189e0f53c06561": {}, "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { "balance": "0xc87826afc2b88254" }, "0xbf5495efe5db9ce00f80364c8b423567e58d2110": { "storage": { "0x0000000000000000000000000000000000000000000000000000000000000035": "0x000000000000000000000000000000000000000000004b7b53b759cac06bc3b2", "0xec8c572bc2e66bfe14376dece39002d6d97d311020c59bc23a93016013e92439": "0x0000000000000000000000000000000000000000000000000f294a8cb09ad9cb" } }, "0xbfe474605fb7f1cf3693a3f21af2e329f7222462": { "balance": "0x269f4a55a240465", "nonce": 5 }, "0xf2f305d14dcd8aaef887e0428b3c9534795d0d60": { "balance": "0xe984e9959d534d3cdf" } }, "pre": { "0x093f6c270ac22ec240f0c6fd7414ea774ca8d3e5": { "balance": "0x1ea3abe47d76b5e00", "code": "..", "nonce": 1 }, "0x2641c2ded63a0c640629f5edf1189e0f53c06561": { "balance": "0x1cb3ca6ff6dc41400", "code": "..", "nonce": 1 }, "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { "balance": "0xc8781d351add1146", "nonce": 797368 }, "0xbf5495efe5db9ce00f80364c8b423567e58d2110": { "balance": "0x0", "code": "...", "nonce": 1, "storage": { "0x0000000000000000000000000000000000000000000000000000000000000035": "0x000000000000000000000000000000000000000000004b7b448e0f3e0fd0e9e7" } }, "0xbfe474605fb7f1cf3693a3f21af2e329f7222462": { "balance": "0x11d7cc57700ae2ed", "nonce": 4 }, "0xf2f305d14dcd8aaef887e0428b3c9534795d0d60": { "balance": "0xe975a599714e5f3cdf", "code": "...", "nonce": 1 } } }, ``` we can always use the precode here if the hashes match * chore: release 0.33.2 * feat: add support for erc7562 tracer (paradigmxyz#392) This aims to resolve this issue : paradigmxyz#391 cc @mattsse * feat: stage revm to 34.0.0 (paradigmxyz#385) stage latest main commit --------- Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> * chore: release 0.34.0 * ci: update to tempoxyz (paradigmxyz#395) * fix: always include storage values for `SLOAD` and `SSTORE` in `GethTrace` (paradigmxyz#389) The default tracer for `debug_*` RPC methods was not reporting warmed storage values. This deviates from Get's implementation and—seemingly—the documented expectation: ```rs /// Represents a storage change during execution. /// /// This maps to evm internals: /// [JournalEntry::StorageChanged](revm::JournalEntry::StorageChanged) /// /// It is used to track both storage change and warm load of a storage slot. For warm load in regard /// to EIP-2929 AccessList had_value will be None. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct StorageChange { /// key of the storage slot pub key: U256, /// Current value of the storage slot pub value: U256, /// The previous value of the storage slot, if any pub had_value: Option<U256>, /// How this storage was accessed pub reason: StorageChangeReason, } ``` In particular, the `SLOAD` for pc `235`, `279`, and `308` are missing `"storage"`. This PR resolves the issue. For reference: [revm-inspectors.json](https://github.com/user-attachments/files/24275105/revm-inspectors.json) [geth.json](https://github.com/user-attachments/files/24275106/geth.json) * test: add prestate replay testing infrastructure (paradigmxyz#399) Add reusable tooling for debugging RPC tracing using prestate tracer responses captured from mainnet transactions. ## Changes - Add `tests/it/repro.rs` with: - `ReproTestFixture`/`TxData` types for deserializing JSON fixtures - `spec_id_from_ethereum_hardfork`/`spec_id_from_block` helpers - `build_db_from_prestate` to populate CacheDB from AccountState - `ReproContext` builder for easy test setup - Add `testdata/repro/tx-selfdestruct.json` fixture from reth's e2e test (mainnet tx 0x391f4b6a...selfdestruct via USDT withdrawal) - Add `alloy-hardforks` dev dependency for block-to-hardfork mapping ## Usage ```rust const FIXTURE: &str = include_str!("../../testdata/repro/my-fixture.json"); #[test] fn test_my_trace() { let ctx = ReproContext::load(FIXTURE); let config = PreStateConfig::default(); let mut inspector = TracingInspector::new( TracingInspectorConfig::from_geth_prestate_config(&config) ); let db = ctx.db.clone(); let mut evm = Context::mainnet() .with_db(ctx.db.clone()) .modify_cfg_chained(|cfg| cfg.spec = ctx.spec_id) .build_mainnet() .with_inspector(&mut inspector); let res = evm.inspect_tx(ctx.tx_env()).unwrap(); // ... assertions on trace results } ``` This enables writing repro tests with the flow: **raw transaction + prestate JSON → configure tracer → run → assert** Refs: https://github.com/paradigmxyz/reth/blob/main/crates/ethereum/node/tests/e2e/prestate.rs --------- Co-authored-by: Amp <amp@ampcode.com> * fix: use Default::default() for TransactionInfo for forward compatibility (paradigmxyz#401) Use `..Default::default()` pattern to ensure forward compatibility when new fields are added to `TransactionInfo`. This prepares the crate for upcoming changes to add a `block_timestamp` field. Related: ethereum/execution-apis#729 Co-authored-by: Amp <amp@ampcode.com> * chore: release 0.34.1 * feat: expose refund_counter in `CallTrace` (paradigmxyz#402) Required to be able to recover pre-refund totals from purely just the `CallTrace` for gas introspection in foundry. foundry-rs/foundry#13262 * chore: release 0.34.2 * chore: bump MSRV to 1.91 (paradigmxyz#407) Bump MSRV from 1.86 to 1.91. * feat: implement Clone for DebugInspector (paradigmxyz#406) Implement `Clone` for `DebugInspector` to enable use with `TxTracer`, which requires `Inspector: Clone`. Non-JS variants clone trivially. The `Js` variant reconstructs the `JsInspector` from its stored `code` and `config` — same approach `fuse()` already uses. * devnet2-revm: bump revm patch for release procedure (paradigmxyz#408) ## Summary This PR is part of the **revm release procedure** (`devnet2-revm` chain). It bumps revm-inspectors to revm v35.0.0. ## Changes - Bump `revm` dependency from 34.0.0 to 35.0.0 in `Cargo.toml`. - Adapt to upstream API change: `journal().precompile_addresses()` now returns a type that requires `.iter().copied().collect()` instead of `.clone()`. - Lower `gas_limit` in selfdestruct tests from 100M to 10M to comply with EIP-7825 transaction gas limit cap enforced in revm 35. ## Validation - `cargo check` - `cargo +nightly clippy --workspace --all-targets --all-features` ## Procedure Context This is an intermediate dependency bump in the revm release flow before downstream updates in `alloy-evm` and `reth`. * chore: release 0.35.0 * chore: bump revm to 36.0.0 (paradigmxyz#409) ## Summary - Bump `revm` dependency from 35.0.0 to 36.0.0 ## Test plan - [x] `cargo check` passes - [x] `cargo test` passes (all 37 tests) * chore: release 0.36.0 * fix: omit empty returnData in geth struct log trace (paradigmxyz#411) ## Summary Only include `returnData` in structLog output when the return-data buffer is non-empty, matching geth's behavior. ## Motivation [ethpandaops trace comparison report](https://investigations.ethpandaops.io/2026-01/trace-comparisons/reth/) shows reth emitting `returnData: "0x"` on **every** structLog step (~1.1M occurrences across 500 txs), while geth/erigon/nethermind/besu all omit it. Geth applies two independent gates before including `returnData`: 1. `cfg.EnableReturnData == true` (opt-in config) — we had this 2. `len(ReturnData) > 0` in `toLegacyJSON` — we were missing this ## Changes - `src/tracing/builder/geth.rs`: add `!step.returndata.is_empty()` check alongside the existing `opts.is_return_data_enabled()` gate ## Testing `cargo test --workspace` — all 24 tests pass, clippy clean. Co-Authored-By: Matthias Seitz <19890894+mattsse@users.noreply.github.com> Prompted by: mattsse Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> * feat: add set_transaction_caller and set_transaction_target (paradigmxyz#412) Adds `set_transaction_caller` and `set_transaction_target` to `TracingInspector`, following the same pattern as `set_transaction_gas_used` / `set_transaction_gas_limit`. Custom transaction types (e.g. AA batches) may execute via a multi-call handler where the EVM's `call()` entry point doesn't reflect the actual transaction sender/recipient. This causes `callTracer` to report zeroed `from`/`to` on the root frame. These methods allow the execution layer to correct the root trace after execution. Co-Authored-By: zhygis <5236121+Zygimantass@users.noreply.github.com> Prompted by: zygis --------- Co-authored-by: zhygis <5236121+Zygimantass@users.noreply.github.com> Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com> * chore: release 0.36.1 * chore: relax `CTX` bounds on `StorageInspector` `Inspector` impl (paradigmxyz#416) Relax `CTX` bounds on `StorageInspector` `Inspector` impl as it doesn't use `Context`. * fix: eip-7708 logs (paradigmxyz#413) CallLog discarded the original Log.address during conversion and geth_empty_call_frame() reconstructed it from execution_address(). This is wrong for any log whose emitter differs from the call frame's execution context — most notably EIP-7708 system-level ETH transfer logs, which are emitted from SYSTEM_ADDRESS (0xfffffffffffffffffffffffffffffffffffffffe) rather than the executing contract. Changes src/tracing/types.rs - Add address: Address field to CallLog to preserve the original log emitter - Update From<Log> to store log.address instead of discarding it - Fix geth_empty_call_frame() to use log.address instead of self.execution_address() tests/it/geth.rs - test_geth_calltracer_logs_eip7708: value-transferring CALL to a precompile under AMSTERDAM — verifies the log address is ETH_TRANSFER_LOG_ADDRESS, not the contract or precompile address - test_geth_calltracer_logs_address_regular: regular LOG0 emission — verifies the log address is the emitting contract - test_geth_calltracer_logs_delegatecall: DELEGATECALL regression — verifies the log address is the proxy (execution context), not the implementation (bytecode) address Breaking CallLog is a public struct with a new address: Address field. Downstream consumers that construct CallLog manually will need to provide this field. Co-authored-by: Kai Yu <kai.yu@Kai-Yu-MacBook-Pro.local> * chore: bump alloy to 2.0 (paradigmxyz#421) Bump alloy dependencies to released 2.0.0. No `[patch.crates-io]` needed. Supersedes paradigmxyz#419 * chore: release 0.37.0 * tip1016: revm state-gas integration (paradigmxyz#405) ## Summary - Update revm rev to `e3223d5b38f7affb901601ed917fa7cdc466be74` (rakita/state-gas branch) for TIP-1016 dual-limit gas accounting ## Test plan - [x] `cargo check` compiles clean - [x] `cargo +nightly clippy` passes - [x] `cargo nextest run` — all 37 tests pass --------- Co-authored-by: Federico Gimenez <federico.gimenez@gmail.com> * chore: release 0.38.0 * fix: check if opcode is valid (paradigmxyz#422) TracingInspector creates OpCodes via `new_unchecked` at `start_step` to handle unknown/custom opcodes. In `step_end`, `step.op.outputs()` calls `OpCode::info()` which panics with `"opcode not found"` when the opcode byte has no entry in `OPCODE_INFO`. Guard with `is_valid()` — unknown opcodes default to 0 outputs. Prompted by: Dragan --------- Co-authored-by: rakita <13179220+rakita@users.noreply.github.com> Co-authored-by: rakita <rakita@users.noreply.github.com> * chore: release 0.38.1 * fix(tracing): always include refund counter, wire enableReturnData config (paradigmxyz#424) Two fixes based on the [ethpandaops trace comparison](https://investigations.ethpandaops.io/2026-01/trace-comparisons/): 1. Always include the refund counter in struct logs, even when zero. Previously omitted when value was 0, causing missing fields in trace output. 2. Wire `enableReturnData` from `GethDefaultTracingOptions` through to `TracingInspectorConfig::record_returndata_snapshots`. Previously this option was ignored, so returnData was never captured even when explicitly requested. Verified on a live reth archive node: - Refund: 22,476/67,028 steps → **67,028/67,028** steps - returnData with `enableReturnData: true`: 0 steps → **24,537** steps * fix(tracing): align memory encoding and returnData gating (paradigmxyz#425) Two fixes to align with the [opcode tracer spec](ethereum/execution-apis#762): 1. Memory chunks now use `0x`-prefixed `bytes32` encoding. Previously `convert_memory()` used bare `hex::encode` without the prefix. 2. `returnData` is now included on every step when `enableReturnData` is true, even when the buffer is empty. Previously skipped when empty. --------- Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> * chore: bump revm to 38.0.0 (paradigmxyz#427) ## Summary - Bumps `revm` from `37.0.0` to `38.0.0`. ## Test plan - [ ] `cargo build` passes - [ ] `cargo test` passes - [ ] CI green * chore: release 0.39.0 * fix: use .values() instead of iter with unused key to satisfy clippy * Revert "fix: use .values() instead of iter with unused key to satisfy clippy" This reverts commit d7d6d46. * Reapply "fix: use .values() instead of iter with unused key to satisfy clippy" This reverts commit 43a426e. --------- Co-authored-by: rakita <rakita@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com> Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> Co-authored-by: Alex Pikme <30472093+reject-i@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Wodann <Wodann@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Philippe Dumonet <philippe@dumo.net> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> Co-authored-by: zhygis <5236121+Zygimantass@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Kai Yu <kai.yu@circle.com> Co-authored-by: Kai Yu <kai.yu@Kai-Yu-MacBook-Pro.local> Co-authored-by: Federico Gimenez <federico.gimenez@gmail.com> Co-authored-by: rakita <13179220+rakita@users.noreply.github.com> Co-authored-by: figtracer <me@figtracer.com>
Summary
This PR adds OpenRPC method and schema definitions for:
debug_traceTransactiondebug_traceBlockByNumberdebug_traceBlockByHashScope is intentionally limited to the default opcode (struct) logger output, while still allowing named tracers through
TraceConfig.tracer.It is also based on the investigation done by ethPandaOps on trace-comparisons
Goal
The goal of this PR is to standardize the existing live
debug_trace*opcode tracer contract as one canonical cross-client output:Non-goals
This PR does not:
EIP-3155conformanceIf the ecosystem wants a cleaner or more opinionated trace format than the current live RPC methods provide, that should be a separate effort.
Preserve vs normalize
Preserved from current live RPC practice
gas,failed,returnValue,structLogsopas a human-readable opcode name stringgasandgasCostas JSON integersStandardized by this PR
0x-prefixeduint256quantities0x-prefixedbytes320x-prefixedbytes32errormust be absent when no error occurredreturnDatamust be absent when disabled and valid hex when enabledstructLogs: []{ txHash, result }entriesSLOADandSSTORERelationship to
EIP-3155/EIP-7756This PR borrows the useful parts of the EIP work, but it does not adopt those specs literally.
The main intentional differences are:
output/gasUsed/passgas/gasCostopas a string namestateRoot,time,fork,memSize, and named tracer output schemas out of scopeKey field-level differences
EIP-3155/EIP-7756output,gasUsed,passreturnValue,gas,failedopopNamegas/gasCostmemorybytes32[]stateRoot,time,fork,memSizeStorage semantics
This PR standardizes storage behavior as part of the canonical output:
0x-prefixedbytes32SLOADandSSTOREnullor{}, at all other opcodesLikely client changes
Based on the current tests, the existing PR investigation, and the ethPandaOps comparison work, the most likely alignment work is:
Geth0xprefix to memory chunks and storage keys/values in opcode tracesErigontxHashpairing match the specBesuNetherminderror/storagefields where required and align storage timing/outputRethreturnDatagating and verify storage timing/encoding behaviorThis table is a concrete starting point for client review, not a claim of fully validated implementation diffs across every client version.
Tests
This PR adds focused tests for:
debug_traceTransactionstructLogs: []debug_traceBlockByNumber{ txHash, result }responsesreturnDatagating behaviordebug_traceBlockByHashMost large trace fixtures are
SpecOnly, meaning they validate the standardized wire contract rather than replaying a recorded byte-for-byte dump from one client.That is intentional: the purpose of this PR is to standardize one canonical response shape and semantics for the default opcode tracer, while avoiding overfitting the tests to incidental formatting outside the specified contract.
Recorded fixtures are still used where the expected behavior is small and unambiguous, such as:
Coverage still incomplete
Current coverage still does not fully prove:
refundpresence when non-zeroThe proposal here is still to define one canonical target; this section simply identifies the areas where additional fixtures or client feedback may still be useful.
Current validation status
make build: passgo test ./...intools/: passmake fill: fail (need go-ethereum to conform to the spec as written here)- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -