Skip to content

[codex] add evm-only giga executor path#3583

Open
codchen wants to merge 14 commits into
mainfrom
codex/sei-v3-evm-only-scaffold
Open

[codex] add evm-only giga executor path#3583
codchen wants to merge 14 commits into
mainfrom
codex/sei-v3-evm-only-scaffold

Conversation

@codchen

@codchen codchen commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add giga/evmonly as the final-form EVM-only giga execution path boundary
  • define raw Ethereum RLP block input and commit-neutral outputs as an EVM-native state changeset plus receipts
  • port the core sei-v3-style execution shape directly into giga/evmonly:
    • raw RLP tx parsing and sender recovery
    • go-ethereum execution against an SDK-free native vm.StateDB
    • key-addressable EVM-native balance, nonce, code, and storage state reads
    • deterministic post-block StateChangeSet output
    • Ethereum receipt and per-tx metadata construction
  • add a map-backed MemoryState backend for tests and early integration
  • leave custom precompiles as fail-closed placeholders behind an SDK-free registry/context
  • refresh the README to describe the current implementation, input/output contract, and known limitations

Notes

Custom precompile behavior is intentionally still open. Registered custom precompile addresses return ErrCustomPrecompilesOpen, including through geth's precompile map, so calls fail closed instead of silently executing as empty accounts.

The current port is sequential. The state boundary and changeset shape are intended to be replaceable with the sei-v3 OCC scheduler/store once that layer is brought over.

Historical BLOCKHASH lookups beyond the parent block are not wired yet; BlockHash is currently used for receipt/log metadata.

Validation

go test ./giga/evmonly/...
go test ./giga/...

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.73904% with 238 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.27%. Comparing base (0ff5a52) to head (f1fa418).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
giga/evmonly/state_db.go 70.54% 129 Missing and 18 partials ⚠️
giga/evmonly/occ.go 73.65% 35 Missing and 19 partials ⚠️
giga/evmonly/executor.go 85.71% 16 Missing and 7 partials ⚠️
giga/evmonly/state.go 88.63% 5 Missing and 5 partials ⚠️
giga/evmonly/parser.go 80.00% 3 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3583      +/-   ##
==========================================
- Coverage   59.16%   58.27%   -0.89%     
==========================================
  Files        2262     2176      -86     
  Lines      187009   176783   -10226     
==========================================
- Hits       110643   103024    -7619     
+ Misses      66430    64710    -1720     
+ Partials     9936     9049     -887     
Flag Coverage Δ
sei-chain-pr 75.73% <75.73%> (?)
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
giga/evmonly/config.go 100.00% <100.00%> (ø)
giga/evmonly/parser.go 80.00% <80.00%> (ø)
giga/evmonly/state.go 88.63% <88.63%> (ø)
giga/evmonly/executor.go 85.71% <85.71%> (ø)
giga/evmonly/occ.go 73.65% <73.65%> (ø)
giga/evmonly/state_db.go 70.54% <70.54%> (ø)

... and 168 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJun 30, 2026, 6:12 AM

@codchen codchen changed the title [codex] scaffold evm-only giga executor path [codex] add evm-only giga executor path Jun 15, 2026
@codchen codchen marked this pull request as ready for review June 15, 2026 07:54
@cursor

cursor Bot commented Jun 15, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New block execution and state/receipt semantics are correctness-critical, but the package is isolated and not yet wired into app/app.go; OCC merge/fallback behavior deserves careful review when enabled.

Overview
Introduces giga/evmonly, a Cosmos-free block execution boundary for the final-form giga path: ExecuteBlock takes ordered raw Ethereum RLP txs plus block context and returns a commit-neutral StateChangeSet (post-block balances, nonces, code, storage), receipts, and per-tx metadata—without sdk.Context or Cosmos tx envelopes.

Execution runs through go-ethereum core.ApplyMessage on a new SDK-free nativeStateDB backed by StateReader / MemoryState. Block-level policy covers min gas price, optional nonce skips, and chain config; validation failures abort the whole block, while in-tx EVM failures still produce failed receipts and the block can continue.

When OCCWorkers > 1 and there are no registered custom precompiles, the executor can run txs speculatively in parallel, validate read/write conflicts and block gas, then merge or fall back to sequential execution. Registered custom precompile addresses are wired as fail-closed stubs returning ErrCustomPrecompilesOpen. A README documents the I/O contract, precompile direction, and known gaps (e.g. limited BLOCKHASH).

Reviewed by Cursor Bugbot for commit f1fa418. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread giga/evmonly/executor.go
Comment thread giga/evmonly/state_db.go
@codchen codchen force-pushed the codex/sei-v3-evm-only-scaffold branch from 44a10bc to ef04c0e Compare June 15, 2026 11:25
Comment thread giga/evmonly/parser.go
@codchen codchen force-pushed the codex/sei-v3-evm-only-scaffold branch from ef04c0e to cb338ff Compare June 16, 2026 07:12
Comment thread giga/evmonly/state_db.go
Comment thread giga/evmonly/state_db.go
Comment thread giga/evmonly/executor.go
@codchen codchen force-pushed the codex/sei-v3-evm-only-scaffold branch from 06357b4 to 966d056 Compare June 17, 2026 02:37
Comment thread giga/evmonly/README.md

- sequential execution of the ordered block transaction list
- RLP decoding and sender recovery through go-ethereum signers
- go-ethereum `core.ApplyMessage` execution against an SDK-free `vm.StateDB`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does "SDK" mean Cosmos?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yep

Comment thread giga/evmonly/state_db.go
for _, acct := range s.accounts {
if acct.SelfDestructed {
acct.Code = nil
acct.Storage = map[common.Hash]common.Hash{}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we need to set acct.Created = false?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Created is used to decided whether SelfDestruct6780 should early return, and I think the behavior of SelfDestruct6780 is to not early return if selfdestruct is called multiple times, so I think we shouldn't set Created to false upon selfdestruct (consistent with geth)

Comment thread giga/evmonly/executor.go
Comment on lines +68 to +70
if gasLimit == 0 {
gasLimit = math.MaxUint64
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this dangerous? could someone maliciously set gasLimit = 0 to bypass any limits we may have?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

gasLimit here is the block gas limit, so it'd be decided by consensus and not set by tx senders

Comment thread giga/evmonly/state_db.go
@@ -0,0 +1,653 @@
package evmonly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is a lot of critical state changing code. i think we need a lot of tests here. Could we use AI to generate a ton of unit cases, interleaving ordering of contract creation/deletion, invalid txs (out of gas, invalid state transition, invalid nonce, verify receipt outputs...etc).

@codchen codchen force-pushed the codex/sei-v3-evm-only-scaffold branch from 966d056 to ab82ec3 Compare June 24, 2026 03:37
Comment thread giga/evmonly/state_db.go
@codchen codchen force-pushed the codex/sei-v3-evm-only-scaffold branch from ab82ec3 to 23fc6d3 Compare June 24, 2026 03:53
Comment thread giga/evmonly/state_db.go
Comment thread giga/evmonly/state_db.go
Value: newValue,
Delete: newValue == (common.Hash{}),
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Self-destruct omits unstaged storage

High Severity

When a contract is self-destructed or recreated via CreateAccount, Finalise clears only the in-memory storage map. ChangeSet compares storage using keys from base.Storage, which is populated lazily via ensureStorage. Slots that still exist in the persistent StateReader but were never read or written in that block never appear in the changeset, so ApplyChangeSet leaves orphaned storage at that address.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5e36ab5. Configure here.

@seidroid seidroid Bot 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.

A new, self-contained giga/evmonly package adds an SDK-free EVM-only block executor with a go-ethereum-backed vm.StateDB, changeset output, receipts, and extensive tests. It is not wired into the production app path, but contains two genuine EVM-semantics correctness bugs (EXTCODEHASH for existing code-less accounts, and storage clearing on legacy SELFDESTRUCT/SetStorage) plus a notable per-snapshot deep-copy performance cost that should be addressed before this becomes the live giga path.

Findings: 2 blocking | 5 non-blocking | 3 posted inline

Blockers

  • None at the file/PR level.
  • 2 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • cursor-review.md was empty — the Cursor second-opinion pass produced no output, so only this review and Codex's findings were merged.
  • GetStorageRoot always returns the zero hash, so the CREATE/CREATE2 collision check (nonce != 0 || codeHash != EmptyCodeHash || storageRoot != EmptyRootHash) can miss collisions against a pre-existing account that has storage but no code/nonce. Combined with the GetCodeHash issue this narrows collision detection to nonce-only for storage-only accounts. Edge case, but worth documenting alongside the known limitations.
  • EIP-158/161 empty-account deletion is not implemented in Finalise (touched-but-empty accounts are not pruned). For the value-based changeset this is mostly benign since no diff is recorded, but it diverges from go-ethereum StateDB semantics and is worth a note in the README limitations.
  • Consider adding tests that exercise the two correctness gaps above: EXTCODEHASH against a funded code-less EOA (expect EmptyCodeHash, not 0x0), and a legacy SELFDESTRUCT of a pre-existing contract whose persisted storage slots were never read in-block (expect deletes in the changeset).
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread giga/evmonly/state_db.go
func (s *nativeStateDB) GetCodeHash(addr common.Address) common.Hash {
code := s.GetCode(addr)
if len(code) == 0 {
return common.Hash{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] EXTCODEHASH semantics bug: this returns common.Hash{} (0x0) whenever code is empty, conflating "account does not exist" with "account exists but has no code". go-ethereum's StateDB returns types.EmptyCodeHash (keccak256 of empty) for an existing account with no code and only common.Hash{} for a non-existent account. As written, EXTCODEHASH on a funded/active EOA returns 0x0 instead of the empty-code hash, so contracts observe incorrect account existence/code state. Distinguish existence (e.g. via Exist(addr)) and return types.EmptyCodeHash for existing code-less accounts.

Comment thread giga/evmonly/state_db.go
Delete: len(acct.Code) == 0,
})
}
storageKeys := storageKeyUnion(base.Storage, acct.Storage)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] The changeset only diffs storage keys present in base.Storage/acct.Storage, and base.Storage is populated lazily only for slots read via ensureStorage. When Finalise clears a self-destructed account (legacy SELFDESTRUCT) or SetStorage resets an account, persisted slots that were never read in-block are in neither map, so no delete is emitted for them. Applying the changeset to durable state then leaves stale storage behind. Because the backend is key-addressable with no range reads, the executor cannot enumerate those slots — this needs an explicit account-clear/delete signal in the changeset (or a documented constraint) rather than per-slot diffs.

Comment thread giga/evmonly/state_db.go
return nil
}

func (s *nativeStateDB) Snapshot() int {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Snapshot() deep-copies the entire accounts, base, logs, accessList, transientStates, and preimages on every call, and the EVM snapshots on every CALL/CREATE/STATICCALL. This is O(touched-state) per snapshot and will be a severe performance cliff for any non-trivial contract execution. Acceptable for the sequential placeholder, but flag it as a known cost — a journal/undo-log approach (as in go-ethereum) will be needed before this is the live giga path.

@seidroid seidroid Bot 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.

This new, isolated giga/evmonly EVM-only executor is well-structured and well-tested, but contains correctness bugs that diverge from go-ethereum semantics: stale committed-state reads across transactions, an OCC validation gap on per-tx gas reservation, and incorrect EXTCODEHASH for empty-but-existing accounts. The package is not yet wired into app.go (non-app-hash-breaking), so these are not yet consensus-affecting, but must be fixed before integration.

Findings: 2 blocking | 4 non-blocking | 3 posted inline

Blockers

  • None at the file/PR level.
  • 2 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • cursor-review.md is empty — the Cursor second-opinion pass produced no output, so its findings could not be merged.
  • Performance/observability: in executeBlockOCC, every transaction that pays a non-zero fee writes the coinbase balance, so the read/write conflict index will mark a coinbase conflict on virtually every multi-tx block and force a full fallback to sequential execution. With real (non-zero) base fees / tips this defeats the OCC fast path entirely. Consider special-casing coinbase fee accrual (e.g. commutative balance additions) and/or logging when OCC falls back so the regression is observable.
  • The exported SetTxContext(hash, index) (state_db.go:519) does not set txIndexUint, while the executor relies on the unexported setTxContext(hash, index, indexUint). The exported method is part of the vm.StateDB surface; if any future caller uses it, logs will get a stale/zero TxIndex. Worth unifying or documenting why two variants exist.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread giga/evmonly/state_db.go
func (s *nativeStateDB) GetCommittedState(addr common.Address, key common.Hash) common.Hash {
s.markRead(stateAccessKey{kind: stateAccessStorage, address: addr, slot: key})
s.ensureStorage(addr, key)
return s.baseAccount(addr).Storage[key]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] GetCommittedState always reads from base, which is the pre-block snapshot and is never advanced between transactions (Finalise does not update base). go-ethereum's GetCommittedState must return the slot value as of the start of the current transaction — i.e. it must reflect writes committed by earlier transactions in the same block (geth checks pendingStorage before originStorage). As written, a later transaction's SSTORE net-gas/refund accounting (EIP-2200/3529) uses the block-start value instead of the current-tx-start value, producing incorrect gas used, receipts, and potentially divergent results when two transactions in a block touch the same slot. Add a per-transaction "committed" layer that is advanced on Finalise, rather than reading the block-base directly.

Comment thread giga/evmonly/occ.go
stateDB.enableAccessTracking()
evm := vm.NewEVM(blockCtx, stateDB, chainConfig, vm.Config{}, nil)
stateDB.SetEVM(evm)
gasPool := new(core.GasPool).AddGas(gasLimit)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] Each speculative transaction is given a fresh full-block gas pool (AddGas(gasLimit)), and validateOCCResults only checks cumulative actual gasUsed against the block gas limit (occ.go:137). Sequential execution instead reserves each tx's GasLimit from a shared pool up front (gp.SubGas(msg.GasLimit) in ApplyMessage). These disagree: e.g. two non-conflicting transfers each with GasLimit=90k in a 100k block each use ~21k, so OCC validation passes (42k ≤ 100k) and commits a block that sequential execution would reject with ErrGasLimitReached after the first tx reserves 90k. OCC must reproduce the sequential gas-reservation semantics (track cumulative reserved msg.GasLimit, not just used gas) or it can accept invalid blocks.

Comment thread giga/evmonly/state_db.go
func (s *nativeStateDB) GetCodeHash(addr common.Address) common.Hash {
code := s.GetCode(addr)
if len(code) == 0 {
return common.Hash{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] GetCodeHash returns the zero hash for every account with empty code. Ethereum semantics require zero only for non-existent accounts; an account that exists (non-zero nonce or balance) but has no code must return types.EmptyCodeHash (keccak256 of empty). As written, EXTCODEHASH on a funded codeless account returns 0 instead of EmptyCodeHash, diverging from geth and the existing x/evm keeper. Return types.EmptyCodeHash for existing-but-codeless accounts and zero only when the account is empty/non-existent.

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f1fa418. Configure here.

Comment thread giga/evmonly/occ.go
})
}
if err := group.Wait(); err != nil {
return nil, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

OCC aborts without sequential fallback

High Severity

When OCCWorkers is enabled, speculative transactions each run against the same pre-block state. A later transaction that depends on an earlier one (for example the next nonce from the same sender) can fail during parallel execution, and executeBlockOCC returns that error immediately. Conflict validation falls back to sequential execution, but these execution errors do not, so otherwise valid blocks can fail outright.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f1fa418. Configure here.

Comment thread giga/evmonly/occ.go
}
writes.addAll(result.writeSet)
}
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

OCC ignores per-transaction gas limits

High Severity

Parallel OCC execution gives every transaction its own GasPool funded with the full block gas limit, while validateOCCResults only checks that the sum of reported gasUsed fits the block limit. go-ethereum’s sequential path also requires each transaction’s gas limit to fit the remaining pool after prior transactions. OCC can therefore accept and merge a block that sequential execution would reject with ErrGasLimitReached.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f1fa418. Configure here.

@seidroid seidroid Bot 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.

A new, isolated giga/evmonly EVM-only execution package with strong test coverage, but it contains a clear EVM-correctness bug in the default (sequential) path — GetCodeHash returns zero for existing code-less accounts — plus two OCC divergences from sequential execution (same-sender nonce blocks error out instead of falling back, and per-tx gas-limit accounting is bypassed). Codex's four findings are all valid; Cursor produced no output.

Findings: 2 blocking | 6 non-blocking | 4 posted inline

Blockers

  • None at the file/PR level.
  • 2 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • ./REVIEW_GUIDELINES.md is empty/missing, so no repo-specific standards were applied. ./cursor-review.md is also empty — the Cursor second-opinion pass produced no output; only Codex's review was available to merge.
  • OCC speculative execution shares e.state (a StateReader) across worker goroutines. MemoryState is RWMutex-guarded so it is safe, but the StateReader interface contract does not document a thread-safety requirement; a non-concurrent backend would race under OCC. Consider documenting that StateReader must be safe for concurrent reads.
  • No test exercises a block with two transactions from the same sender under OCC, nor a block where two txs declare large gas limits but use little gas — the exact cases that trigger the two OCC divergences below. Adding these would catch regressions.
  • Codex notes it could not run go test ./giga/evmonly/... due to the sandbox being unable to fetch the Go 1.25.6 toolchain; tests were not executed during review.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread giga/evmonly/state_db.go
func (s *nativeStateDB) GetCodeHash(addr common.Address) common.Hash {
code := s.GetCode(addr)
if len(code) == 0 {
return common.Hash{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] GetCodeHash returns the zero hash for every account with empty code, but per EIP-1052 EXTCODEHASH (and go-ethereum's StateDB.GetCodeHash) must return zero only for accounts that do not exist / are empty. An account that exists (non-zero balance or nonce) but holds no code should return the empty-code hash (keccak256("")). As written, contracts observing such accounts via EXTCODEHASH see incorrect existence, and this affects the default sequential path too (not just OCC). Gate the zero return on account emptiness and otherwise return the empty-code hash. (Raised by Codex.)

Comment thread giga/evmonly/occ.go
}
})
}
if err := group.Wait(); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] OCC speculatively executes every tx against the pre-block state, so a second tx from the same sender (nonce N+1) fails core.ErrNonceTooHigh, and a tx depending on a prior create/write hits missing-state errors. Because executeTxSpeculative returns that error and group.Wait() propagates it here as return nil, err, the whole block fails instead of falling back to sequential — even though it is perfectly valid sequentially. Same-sender multi-tx blocks are extremely common, so OCC is effectively unusable for them. Speculative execution errors should trigger the sequential fallback (like validateOCCResults returning false) rather than aborting ExecuteBlock. (Raised by Codex.)

Comment thread giga/evmonly/occ.go
}, nil
}

func validateOCCResults(results []occTxExecution, gasLimit uint64) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] validateOCCResults only sums actual gasUsed against the block gas limit, but Ethereum's GasPool reserves each tx's full declared gas limit at buyGas time (refunding the unused remainder). Since each speculative tx gets a fresh full-block GasPool, two txs with high gas limits but low gas used can pass OCC validation (sum of gasUsed ≤ limit) while sequential execution would reject the second with core.ErrGasLimitReached. This is a divergence from sequential semantics; the validation needs to model the per-tx gas-limit reservation, not just cumulative gasUsed. (Raised by Codex.)

Comment thread giga/evmonly/state_db.go
if acct.SelfDestructed {
s.recordAccount(addr)
acct.Code = nil
acct.Storage = map[common.Hash]common.Hash{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] On legacy (pre-6780) selfdestruct, Finalise clears acct.Storage, but ChangeSet() diffs against base.Storage, which only contains slots lazily read via ensureStorage. Slots that were persisted but never read during the block are absent from base.Storage, so no delete is emitted for them and applying the changeset leaves stale storage behind. A correct legacy-selfdestruct wipe needs range iteration over the account's full storage, which the README states is intentionally out of scope — so at minimum document this as a known limitation alongside the existing ones (it currently only lists BLOCKHASH). (Raised by Codex.)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants