Skip to content

fix(eth): release the state reference when stateAtTransaction gives up - #2578

Open
gzliudan wants to merge 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:fix-stateat-transaction-release
Open

gzliudan wants to merge 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:fix-stateat-transaction-release

Conversation

@gzliudan

@gzliudan gzliudan commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

stateAtTransaction obtains the parent state with StateAtBlock(..., readOnly=true), which takes a real reference on the live trie database whenever the state is available there, and hands a release function to its caller. Every path that gives up after that returns a nil release instead of calling it, so the reference is dropped rather than released:

  • message build failure (base eth/state_accessor.go:249, now :261)
  • replay failure (base :255, now :267)
  • transaction index out of range (base :261, now :273)

The out of range path is the easiest one to hit: debug_traceTransaction with a bad index replays the whole block and then returns an error, leaking the reference taken on the live trie database. Nothing observes the leak — it is pure reference accounting, with no log, no panic and no failing assertion.

Fix

Take the release handle behind a guard that defaults to releasing and is cancelled only on the paths that hand the release over to the caller:

handedOff := false
defer func() {
    if !handedOff {
        release()
    }
}()

Only the two paths that return the state set handedOff = true, so a newly added error path cannot leak again.

Upstream

Historical issue rather than one introduced on top of the base: geth's stateAtTransaction (ethereum/go-ethereum@854fbb8ce, eth/state_accessor.go:232-277) drops the release on the same three error paths, so there is no upstream fix to port and no geth PR to reference.

Tests

TestStateAtTransactionGiveUpReturnsNoState drives the out of range path and pins what is observable from outside: the call returns nothing half-initialised (no transaction, no state and no release function). Whether the reference is actually released is deliberately not asserted — the release function is created inside StateAtBlock and cannot be observed or injected — so that part of the fix rests on the deferred block being executed, not on an assertion.

make all, go test ./eth/ -run TestStateAtTransaction, make quick-test, make test and gofmt are clean.

Relation to other work

A companion branch #2579 changes the replay inside this same function so that it follows the block processing routing. Both branches touch eth/state_accessor.go; merging this one first keeps the other a trivial rebase.

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4ca39870-8563-4306-9c8b-2ed5bb7c98ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The regression test does not verify that the release callback executes and would pass without the fix.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Fixes leaked trie-state references when transaction-state replay exits with an error.

Changes:

  • Releases acquired state unless ownership is returned to the caller.
  • Adds an out-of-range transaction test.
File summaries
File Description
eth/state_accessor.go Guards state-reference ownership and cleanup.
eth/state_accessor_test.go Exercises the out-of-range error path.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eth/state_accessor_test.go
stateAtTransaction obtains the parent state through StateAtBlock with readOnly
set, which takes a real reference on the live trie database whenever the state
is available there. The three paths that give up after that returned without
releasing it: the message build failure, the replay failure and the out of
range index. Only the paths that return the state hand the release function
over to the caller, so the reference taken by every give-up path was leaked.

Release it with a guard that defaults to releasing and is cancelled when the
release function is handed over instead, so a newly added error path cannot
leak again.

Historical issue rather than one introduced here: the baseline and upstream
geth both drop the release function on the same paths (no upstream fix to
port). It is reachable through the debug methods that go through
stateAtTransaction -- debug_traceTransaction, debug_traceCall and
debug_storageRangeAt -- each of which exits with an error after the state has
been obtained.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused guard correctly covers all post-acquisition error paths while preserving successful ownership transfer.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants