Conversation
Host::access_account warms up an account which does not exist by inserting a temporary empty one, but journaled its flags after the insertion, recording the account as already existing. A revert therefore left it in the state and build_diff() reported it in StateDiff::deleted_accounts, deleting an account which has never existed. Applying such a diff is a no-op, so the produced state has always been correct. Journal the new account instead, as create() and execute_message() do.
find() returned nullptr for a missing account, so callers handled absence separately and nothing could be recorded about an account which does not exist. Return a reference always and let the nonexistent flag answer "does this account exist?". The miss is then cached, a cold access warms the account without creating it, and journal_account_flags reverts the flag like any other, replacing journal_new_account. Reverting to nonexistent also clears the value fields left by a reverted CREATE, now reachable through the reference.
An account which has never existed can appear in StateDiff as modified or deleted without changing the state the diff produces, so the post state alone cannot tell. Add Expectation::diff_excludes listing addresses which must not appear in the diff at all, and use it for the reverted cold access.
Contributor
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## state/access-account-journal-order #1712 +/- ##
===================================================================
Coverage 98.02% 98.03%
===================================================================
Files 180 179 -1
Lines 16379 16368 -11
Branches 3769 3760 -9
===================================================================
- Hits 16056 16046 -10
Misses 243 243
+ Partials 80 79 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Base automatically changed from
state/access-account-journal-order
to
master
September 15, 2026 07:32
This branch has not been deployed
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.
Stacked on #1709 — review the top two commits.
State::find()returnednullptrfor a missing account, so every caller split into two cases and nothing could be recorded about an account which does not exist.get()now always returnsAccount&and thenonexistentflag is the answer to "does this account exist?".What falls out of that:
find()TODO.StateDiffat all, which removes the leftoverdeleted_accountsentry that state: Restore account nonexistence when a cold access is reverted #1709 only removes for the reverted case.journal_new_accountis gone. It hardcodedaccess_status = COLD, which is wrong once a nonexistent account can already be warm;journal_account_flagssnapshots the real flags and revertsnonexistentlike any other flag.insert()materializes in place, carrying overaccess_status,storageandtransient_storage. Replacing the node instead discards the storage slots the tx access list warmed on it (state.cppwarms slots right afteraccess_account) — measured as +2100 gas increate2_rollback_preserves_access_list_slot_warmth.nonexistentclears the value fields a reverted CREATE used to leave behind. They were unreachable throughfind(), but are readable through the returned reference, so without thisEXTCODEHASH/EXTCODESIZE/BALANCEon a reverted-create address would return the deployed contract's data.Expectation::diff_excludesThe second commit lets
state_transitiontests inspect the diff. An account which has never existed can appear inStateDiffas modified or deleted without changing the state the diff produces, so the post state alone cannot tell — the fixture applies the diff and compares the result, making a no-op delete invisible.diff_excludeslists addresses which must appear in neither list. This supersedes the standalonestate_diff_test.cppadded in #1709, which is deleted here in favour of a declarative test:A blanket "every deleted account existed in pre" fixture invariant is not available instead: an account created and emptied within the transaction legitimately violates it (the
massdestructtests).Verification
1292 unit tests with
-DASSERTIONS=ON, Release build clean, EESTtests-20018172 state and 8612 blockchain. The declarative test was mutation-tested: restoring materialize-before-journal makes it fail with0x…ab5e17: deleted in the state diff.