fix: prevent GetStorageRoot from polluting contract cache and causing incorrect state writeback#4803
Merged
envestcc merged 3 commits intoiotexproject:masterfrom Mar 17, 2026
Merged
Conversation
…teback The geth upgrade introduced a GetStorageRoot call in evm.create() for contract creation collision checks. The previous implementation used getContractWoCreate which added the target account to cachedContract. This caused CommitContracts to write back the account with a recomputed non-zero empty-trie Root (via Snapshot), diverging from v2.3.3 behavior where Root remained zero. Fix: read the account Root directly without creating a contract trie or adding to the cache. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add AlwaysWriteCachedContract feature flag (gated by ToBeEnabled fork height, true before fork). When disabled (after fork), CommitContracts skips both Commit() and PutState() for contracts that were only read (not modified by SetState/SetCode), preventing read operations from causing unnecessary writes through the cachedContract → Snapshot → CommitContracts chain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CoderZhi
approved these changes
Mar 16, 2026
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
When validating historical blocks, a "delta state digest doesn't match" error occurs. The root cause is that
the geth upgrade (v1.7.4-0.20260114032628) introduced a
GetStorageRoot(address)call inevm.create()forcontract creation collision detection. The previous
GetStorageRootimplementation calledgetContractWoCreate, which created a contract trie and added the target account tocachedContract— evenfor non-contract (EOA) accounts.
This triggered the following chain of events:
GetStorageRoot(addr)→getContractWoCreate(addr)→ account added tocachedContractSnapshot()→ iterates all cached contracts → computestrie.RootHash()for the empty storagetrie → writes non-zero hash to
Account.Rooton the original contract objectCommitContracts()writes back all cached contracts viaPutState, including this unmodified accountwith a now non-zero Root
In v2.3.3 (old geth),
GetStorageRootdid not exist, so the account was never cached and its Root stayedzero.
Fix
Commit 1: Fix GetStorageRoot (immediate fix)
Modified
GetStorageRootto read the account's Root field directly viaaccountutil.Recordedwithoutcreating a contract trie or adding to
cachedContract. Removed the now-deadgetContractWoCreatefunction.Commit 2: Skip writing read-only contracts (forward-looking hardening)
Added
AlwaysWriteCachedContractfeature flag (gated byToBeEnabledfork height). When disabled (afterfork activation),
CommitContractsskips bothCommit()andPutState()for contracts that were only read(not modified by
SetState/SetCode). This prevents any future read operation from inadvertently causingstate writes through the
cachedContract → Snapshot → CommitContractschain.Dirty()method toContractinterface to checkdirtyCode || dirtyState