Skip to content

state: Avoid redundant lookups and allocations#1584

Open
ywy2090 wants to merge 1 commit into
ipsilon:masterfrom
ywy2090:state-minor-opts
Open

state: Avoid redundant lookups and allocations#1584
ywy2090 wants to merge 1 commit into
ipsilon:masterfrom
ywy2090:state-minor-opts

Conversation

@ywy2090

@ywy2090 ywy2090 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

A collection of small optimizations on the state transition hot paths:

  • Skip the initial state storage query for accounts without initial storage (e.g. newly created contracts): the value is guaranteed to be zero. A constructor storing N slots previously made N pointless StateView::get_storage() queries.
  • Bind the sender account reference once in execute_message() instead of three get() lookups per value-transferring call (references to unordered_map elements are stable, inserting the recipient doesn't invalidate them).
  • Look up the 0x03 quirk account on the revert path only for revisions where the quirk applies (Spurious Dragon and later).
  • Return early from access_account() for precompiles: they are always warm (EIP-2929) and no state entry is needed for them. Note a small observable difference: untouched precompile addresses no longer appear in the state diff as no-op modified/deleted entries (the resulting post-state is identical).
  • Build the EIP-7702 delegation designation in a stack buffer instead of concatenating temporary heap allocations per authorization.
  • Reserve StateDiff::modified_accounts, pass bytes_view by value.

- Skip the initial state storage query for accounts without initial
  storage (e.g. newly created contracts): the value is guaranteed to
  be zero. A constructor storing N slots previously made N pointless
  StateView queries.
- Bind the sender account reference once in execute_message() instead
  of three get() lookups per value-transferring call.
- Look up the 0x03 quirk account on the revert path only for revisions
  where the quirk applies (Spurious Dragon and later).
- Return early from access_account() for precompiles: they are always
  warm (EIP-2929) and no state entry is needed for them. The access
  list warming loop in transition() must now materialize the account
  itself before warming storage keys, as the access list may name a
  precompile together with storage keys (covered by a new test).
  As a side effect untouched precompile addresses no longer appear in
  the state diff as no-op modified/deleted entries.
- Build the EIP-7702 delegation designation in a stack buffer instead
  of concatenating temporary heap allocations per authorization.
- Reserve StateDiff::modified_accounts, pass bytes_view by value.
@ywy2090
ywy2090 force-pushed the state-minor-opts branch from 3df42c9 to 3670f3c Compare July 8, 2026 10:59
@ywy2090

ywy2090 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

The execution-spec-tests / precompiles-gmp / precompiles-libsecp256k1 failures were a real bug in the first push: the access-list warming loop in transition() relied on access_account() materializing the account before get_storage() is called, which the precompile early-return broke — an access list naming a precompile with storage keys (EEST eip2930_access_list.test_transaction_intrinsic_gas_cost) then dereferenced a null account and crashed.

Fixed by materializing the account in the warming loop when storage keys are present, and added a unit test (state_transition.access_list_precompile_with_storage_keys) reproducing the crash (verified under ASan+UBSan: SEGV before the fix, 1130/1130 tests pass after).

@chfast chfast left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Generally these look good (except for inline comments), but please submit them as separate PRs (you can reuse this PR for one of them).

Comment thread test/state/host.cpp
const auto is_03_touched = acc_03 != nullptr && acc_03->erase_if_empty;
// The 0x03 quirk applies only from Spurious Dragon, don't look the account up earlier.
bool is_03_touched = false;
if (m_rev >= EVMC_SPURIOUS_DRAGON)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not worth it because we mostly care about the performance in the recent forks. However, you can check if it is enough to apply the quirk only in the single fork (where it happen on Mainnet).

Comment thread test/state/state.cpp
if (missing)
// Query the initial state only if the account may have storage there;
// otherwise the initial value is guaranteed to be zero (the default).
if (missing && acc.has_initial_storage)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like edge case which in unlikely in practice and we don't use .has_initial_storage as it is only needed for CREATE collisions (another edge case).

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.

2 participants