Skip to content

execution/commitment: fix parallel commitment wrong trie root (re-land #22257 + storage-fold nav path fix) - #22495

Merged
mh0lt merged 10 commits into
mainfrom
alex/commitment_navpath_36
Jul 19, 2026
Merged

execution/commitment: fix parallel commitment wrong trie root (re-land #22257 + storage-fold nav path fix)#22495
mh0lt merged 10 commits into
mainfrom
alex/commitment_navpath_36

Conversation

@AskAlexSharov

@AskAlexSharov AskAlexSharov commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Parallel commitment computes a wrong trie root at block 1-2 on main. With ERIGON_COMMITMENT_PARALLEL=true, 11 tests fail (execution/tests x7, execution/verify, execution/execmodule, execution/state/genesiswrite, rpc/jsonrpc).

Bisected to #22362, which reverted #22257. Parallel commitment needs #22257; main lost it.

#22257 was reverted because it wedged mainnet block 5227811 (mount[f] build: unfold: empty branch data read during unfold). That bug is fixed here.

The bug

fillFromLowerCell synced hashedExtension on every prepended extension. When a storage row (lowDepth > 64) propagates into an account leaf, this guard is skipped:

if lowCell.accountAddrLen > 0 || lowDepth < 64 {   // skipped when lowDepth > 64
    cell.accountAddrLen = lowCell.accountAddrLen
}

so the cell is still the account cell, and the sync overwrote its keccak-derived account nav path with storage nibbles. The next unfold followed the bogus path to a prefix owning no branch record.

It fires on main today, in an existing test:

accLen=20 stoLen=0 lowDepth=65 oldHashedExtLen=0 newExtLen=1
   in TestDeepFold_BranchSurvivorCollapse

Fix: sync only when the cell has no plain key — only such a cell navigates by its extension.

Commits

  1. Plain revert of Revert #22257: parallel-commitment mount empty-branch regression #22362 (reviewable apart from the fix). Keeps the unrelated benchmarks CI: occasional 1.5-5.5h stalls — mmap page-fault crawl in etl mergeSortFiles during BenchmarkPruneSmallBatches flush #22361 short-mode guard that Revert #22257: parallel-commitment mount empty-branch regression #22362 carried in.
  2. The fix + red-first tests.
  3. Modernize counted loops (re-landed file predates all: enable omitzero and rangeint analyzers under modernize linter #22449 enabling rangeint).

Status

Full suite green in both modes — only cmd/utils/app x2, which fail identically on plain main.

[wip]: not verified against mainnet block 5227811. That needs the from-genesis re-exec A/B that produced #22362. The mechanism #22362 named is shown real and fixed; the link to that block is inference.

cc @awskii

Restores the trie-state round-trip fixes reverted by #22362, which left
parallel commitment unable to compute a correct root: with
COMMITMENT_PARALLEL=true main fails 11 tests across execution/tests,
execution/verify, execution/execmodule, execution/state/genesiswrite and
rpc/jsonrpc with a wrong trie root at block 1-2.

The corruption that motivated the revert is fixed in the follow-up commit;
this one is a plain revert of #22362 so the two are reviewable apart.

The #22361 short-mode guard in db/test/domain_shared_bench_test.go, which
#22362 carried in alongside the revert, is deliberately left in place — it
is unrelated to the trie work and still needed.

Reverts #22362.
…agate fold

fillFromLowerCell's only caller, foldPropagate, resets upCell.extLen but not
upCell.hashedExtLen. When a storage row (lowDepth > 64) propagates into an
account leaf, the opening guard `lowCell.accountAddrLen > 0 || lowDepth < 64`
is skipped, so the cell stays the account cell; the prepend branch still fires
via `lowCell.storageAddrLen == 0 && lowDepth > 64` and the sync then overwrote
that cell's keccak-derived account navigation path with an extension in storage
nibble space. The next unfold followed the bogus path to a prefix owning no
branch record and failed with "empty branch data read during unfold" — the
mount[f] failure at mainnet block 5227811 that #22362 reverted #22257 for.

Sync only when the cell carries no plain key: such a cell navigates by its
extension, so the nibbles are also its unfold path. A cell with a plain key
navigates by the derived path and must keep it.

The other suspect named by #22362, the SetState leaf-root derive, only fires
when the root itself is a leaf, so it is a no-op on a trie with many accounts
and is left as #22257 wrote it.

Not verified against mainnet block 5227811: reproducing that needs a
from-genesis re-exec with --experimental.parallel-commitment on mainnet data.
…trip tests

The re-landed test file predates #22449, which enabled the rangeint analyzer,
so its counted loops fail lint against main's config. Convert them to range
over int.

Copilot AI left a comment

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.

Pull request overview

Re-lands prior trie state round-trip fixes needed for parallel commitment correctness, and patches a storage-fold navigation-path corruption that could lead to “empty branch data read during unfold” (notably affecting parallel commitment roots at very early blocks).

Changes:

  • Add SeedRootFrom + root seeding in StreamingCommitter so restarted/restored templates correctly seed the next base when the on-disk root has no branch record.
  • Fix fillFromLowerCell to only sync hashedExtension when the cell has no plain key, preventing storage-extension nibbles from overwriting account navigation paths.
  • Add regression tests for restart parity / propagate-folded roots and for the storage-fold nav-path bug; refactor testkit to carry state blobs between batches.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
execution/commitment/streaming_commitment.go Adds root seeding (SeedRootFrom) and uses it when building fresh bases; refactors root unfolding via unfoldRootWall.
execution/commitment/storage_fold_navpath_regression_test.go New regression tests ensuring storage-fold propagation doesn’t corrupt account navigation paths, while account-branch cells still sync nav paths.
execution/commitment/state_roundtrip_regression_test.go New restart-lifecycle parity tests across sequential/parallel/streaming engines, including propagate-root and seed-change scenarios.
execution/commitment/parallel_testkit_test.go Updates testkit to carry EncodeCurrentState/SetState blobs across batches and to seed streaming bases via SeedRootFrom.
execution/commitment/parallel_patricia_hashed.go Seeds streaming committer base from the template root each Process to keep SetState restore target and committer in sync.
execution/commitment/parallel_mount.go Introduces unfoldRootWall helper and uses it to avoid over-unfolding restored root extensions.
execution/commitment/hex_patricia_hashed.go Fixes fillFromLowerCell nav-path sync condition; hardens needUnfolding, sets rootPresent on propagate folds, and re-derives leaf-root nav paths in SetState.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread execution/commitment/streaming_commitment.go
… splits

TouchKey inserts into sc.splits under trieMu's write lock. SeedRootFrom ranged
over the same map with no lock, so a touch concurrent with a reseed hits the
runtime's "concurrent map iteration and map write" fatal error.

Take trieMu.RLock for the iteration: it excludes TouchKey's write lock, and the
trieMu -> s.mu order matches the existing scheduler path.
Cover the guard's third arm: a keyless cell deep in storage still navigates by
its extension and must sync, so the skip is keyed on the plain key rather than
on depth. Without it, tightening the guard to lowDepth < 64 passes.

Also pin the expected nibbles instead of comparing extLen to hashedExtLen, and
use length.Addr rather than a bare 20.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

… churn

The guard `accountAddrLen == 0 && storageAddrLen == 0` reads on its own; the why
lives in the fix commit's message. Append rootSeeded to the existing root-field
block instead of reordering it, so the struct diff is a pure add.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

@AskAlexSharov

Copy link
Copy Markdown
Collaborator Author

CI: eest-spec-zkevm-witness red — confirmed flake, not a regression

The failure looks alarming on a commitment PR (Wrong trie root of block 1 on set_code_to_sstore_then_sload), so here is the proof it is not caused by this PR's code:

  • The two previous commits on this branch (5d2cea1c1, 279ec2af2) passed both eest-spec-zkevm-witness and eest-spec-zkevm-witness-race.
  • Those commits carry identical commitment behavior to HEAD — HEAD differs only by removing a comment and reordering struct fields (gofmt-verified, zero runtime effect). A comment removal cannot change a trie root.
  • The log shows 57 BAL mismatch … blockBalHash=0 lines: eest fixtures carry no BAL, so the parallel executor's speculative BAL "mismatches" the empty declared one. This is the known parallel-exec BAL nondeterminism, independent of the commitment trie this PR touches.
  • Only 1 of 23,822 tests failed, and the identical-behavior commit cleared it twice.

So it is the parallel-executor BAL speculative-execution flake surfacing on one EIP-7702 storage case, not the re-landed #22257 or the nav-path guard. Re-running the failed shard once the run completes.

The kurtosis / assertoor_regular_parallel_test failure is a separate devnet/assertoor integration flake (an assertoor task failure, no code signature); also a re-run.

Neither is in ci-gate.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

@mh0lt
mh0lt added this pull request to the merge queue Jul 19, 2026
Merged via the queue into main with commit aa0b6f8 Jul 19, 2026
94 checks passed
@mh0lt
mh0lt deleted the alex/commitment_navpath_36 branch July 19, 2026 12:04
mh0lt added a commit that referenced this pull request Jul 19, 2026
execution/state: crypto.HashData -> crypto.Keccak256Hash (HashData removed on main by #22489; identical single-[]byte signature). Also inherits #22495 parallel-commitment nav-path fix.
@AskAlexSharov AskAlexSharov changed the title [wip] execution/commitment: fix parallel commitment wrong trie root (re-land #22257 + storage-fold nav path fix) execution/commitment: fix parallel commitment wrong trie root (re-land #22257 + storage-fold nav path fix) Jul 20, 2026
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.

3 participants