Skip to content

[SharovBot] fix: prestate tracer missing zero-codeHash accounts in post diffMode state#20656

Open
erigon-copilot[bot] wants to merge 3 commits intorelease/3.4from
fix/prestate-tracer-codehash-release-3.4
Open

[SharovBot] fix: prestate tracer missing zero-codeHash accounts in post diffMode state#20656
erigon-copilot[bot] wants to merge 3 commits intorelease/3.4from
fix/prestate-tracer-codehash-release-3.4

Conversation

@erigon-copilot
Copy link
Copy Markdown
Contributor

[SharovBot]

Problem

In debug_traceBlockByNumber (and related RPC methods) using the prestateTracer in diffMode, accounts that are touched for the first time during a transaction (e.g. the target of a CALL with zero balance) should appear in the post state with codeHash: 0x0. Erigon was not including these entries, while the reference node (go-ethereum) was.

Failing CI test: mainnet-rpc-integ-tests-latestdebug_traceBlockByNumber/test_42.json (consistent across all 10 recent release/3.4 commits)

Root Cause

In processDiffState(), the comparison to detect codeHash changes used common.Hash{} (all zeros) as the default prevCodeHash. For non-existent accounts, newCodeHash was also computed as common.Hash{} (since GetCode returns empty bytes). This meant prevCodeHash == newCodeHashmodified = false → the account was pruned from both pre and post.

go-ethereum uses EmptyCodeHash (keccak256("") = 0xc5d246...) as the default prevCodeHash. For non-existent accounts, GetCodeHash returns 0x0 (NilCodeHash), so 0x0 != EmptyCodeHashmodified = true → the account appears in post with codeHash: 0x0.

Fix

Mirror go-ethereum's logic in processDiffState():

  1. Use Exist() to distinguish non-existent accounts (codeHash = 0x0) from existing codeless accounts (codeHash = EmptyCodeHash).
  2. Use accounts.EmptyCodeHash as the default prevCodeHash when no codeHash is stored in the pre-state.

Testing

  • go build ./execution/tracing/tracers/native/
  • go test ./execution/tracing/... ✅ all pass
  • This fix addresses the consistent debug_traceBlockByNumber/test_42 failure in mainnet-rpc-integ-tests-latest CI

…fMode

In diffMode, when a non-existent account is touched by a transaction (e.g., as
the target of a CALL), the account should appear in the post state with
codeHash=0x0 — matching go-ethereum's prestateTracer behaviour.

Previously, processDiffState used common.Hash{} (all zeros) as the default
prevCodeHash when no codeHash was stored for a pre-state account. For
non-existent accounts, GetCode returns empty, so newCodeHash was also computed
as common.Hash{} — the comparison prevCodeHash == newCodeHash meant the account
was never marked as modified and thus excluded from the post state.

go-ethereum uses EmptyCodeHash (keccak256("")) as the default prevCodeHash, so
for a non-existent account where GetCodeHash returns 0x0 (NilCodeHash), the
comparison 0x0 != EmptyCodeHash triggers modified=true, and the account appears
in post with codeHash=0x0.

Fix: mirror go-ethereum's logic by:
1. Using Exist() to distinguish non-existent accounts (codeHash=0x0) from
   existing codeless accounts (codeHash=EmptyCodeHash).
2. Using accounts.EmptyCodeHash as the default prevCodeHash when no codeHash
   is set in the pre-state.

This fixes debug_traceBlockByNumber diffMode prestate tests that expect the
reference implementation output (e.g. test_42 in erigon-qa).

Co-authored-by: Giulio Rebuffo <giulio.rebuffo@gmail.com>
…shing pre-existing codeless accounts from non-existent accounts in lookupAccount

Co-authored-by: Giulio Rebuffo <giulio.rebuffo@gmail.com>
@erigon-copilot
Copy link
Copy Markdown
Contributor Author

[SharovBot] pushed a follow-up fix to this branch.

Problem: The previous commit changed prevCodeHash to default to EmptyCodeHash in processDiffState, but this broke debug_traceBlockByNumber/test_33.tar and test_34.tar — accounts that existed with no code before the tx were no longer distinguishable from accounts that didn't exist at all.

Root cause: In lookupAccount, existing codeless accounts had their CodeHash set to nil, same as non-existent accounts. In processDiffState, a nil CodeHash was then ambiguous.

Fix: Moved the Exist() check into lookupAccount itself: existing codeless accounts now get EmptyCodeHash stored, while non-existent accounts keep nil CodeHash. processDiffState reverts to the natural common.Hash{} default for prevCodeHash — so:

  • Existing codeless pre-tx: prevCodeHash=EmptyCodeHash = newCodeHash=EmptyCodeHash → not in diff ✓
  • Non-existent pre-tx + touched: prevCodeHash=0x0newCodeHash=EmptyCodeHash → in diff with codeHash=0x0 ✓ (test_42 fix preserved)

…es that broke unit tests

The previous commits introduced Exist() checks in both lookupAccount and
processDiffState to distinguish pre-existing codeless accounts from
non-existent ones using EmptyCodeHash. This caused codeHash to leak into
JSON output for EOAs and produced spurious codeHash diffs for accounts
that went from non-existent to existent-but-codeless (e.g. coinbase).

The fix simplifies the logic: only compute a non-zero codeHash when there
is actual code. For codeless accounts (whether existing or not), codeHash
remains zero/nil, which correctly omits it from JSON and avoids false diffs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Co-authored-by: Giulio Rebuffo <giulio.rebuffo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants