Skip to content

perf: consolidate WorldDiff (address,key) maps to cut verifier memory#105

Merged
0xVolosnikov merged 3 commits into
vv/memopt-on-popzxcfrom
vv/merge-slot-maps
Jun 15, 2026
Merged

perf: consolidate WorldDiff (address,key) maps to cut verifier memory#105
0xVolosnikov merged 3 commits into
vv/memopt-on-popzxcfrom
vv/merge-slot-maps

Conversation

@0xVolosnikov

@0xVolosnikov 0xVolosnikov commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Reduces WorldDiff memory on large mainnet batches (consumer: matter-labs/eravm-airbender-verifier#18) by removing duplicated (address, key) keys across its maps. Stacked on #104 (vv/memopt-on-popzxc).

Scope note: this PR now contains both consolidation steps — the experimental "Group A" (#106) was merged into this branch, so it's no longer separate. Both are described below.

Changes

  • Group B — merge the three membership sets (read_storage_slots, written_storage_slots, committed_reads_at_depth_zero) into one slot_flags: RollbackableMap<(H160,U256), u8> of bit flags. External-rollback semantics preserved; public committed_reads_at_depth_zero_iter kept (now filters the flag).
  • Group A — merge the two internal-rollback write maps (storage_changes: U256 + paid_changes: u32) into one storage_writes: RollbackableMap<(H160,U256), StorageWriteEntry { value, paid }>. transient_storage_changes left separate (distinct keyspace).

⚠️ Public API change

WorldDiff::get_storage_state() now returns &BTreeMap<_, StorageWriteEntry> (was …, U256>), and StorageWriteEntry is re-exported from the crate root. Direct callers must project .value. The downstream consumer is eravm-airbender-verifier's vm_fast (2 sites) — its vm2 pin bump must land together with that .value projection.

Correctness

  • Rollback groups unchanged (slot_flags external; storage_writes internal).
  • write_storage does a single insert per path; prepaid reuses the prior entry value (no redundant lookup/history).
  • 55 lib tests pass, including the storage_changes_* proptests, the Boojum storage-log trace tests, and a new merged_storage_write_tracks_paid_and_rolls_back covering non-zero prepaid + rollback.

Measured impact

On the eravm guest, the worst-case production batch (67912) drops from needing ~920–952 MiB to fitting at ~720 MiB (~200 MiB off peak) — turning a <32 MiB margin into comfortable headroom.

Review items addressed

P2 (re-export StorageWriteEntry), P3 (single insert in write_storage; add non-zero-paid test), and stale doc-comment field-name refs — all in 1044b47. P1 (scope) addressed by this description; the API break is called out above for the coordinated eravm change.

This comment was marked as resolved.

Follow-up to #104 (shamatar's suggestion). Merge `read_storage_slots`,
`written_storage_slots`, and `committed_reads_at_depth_zero` — three
`RollbackableSet<(H160, U256)>` — into one `RollbackableMap<(H160, U256), u8>`
of access flags, storing the 52-byte `(address, key)` once instead of 3x.

External-rollback semantics and the public `committed_reads_at_depth_zero_iter`
accessor are preserved. 54 lib tests pass (incl. the storage-log trace tests).

Measured on the eravm-airbender-verifier guest: the worst-case 67xxx production
batch drops from needing ~920-952 MiB to fitting in 768 MiB (~150+ MiB saved),
on top of #104.

This is "Group B" (membership sets). "Group A" (storage_changes / paid_changes /
transient_storage_changes) is a separate, hot-path change to evaluate next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0xVolosnikov and others added 2 commits June 15, 2026 12:42
> ⚠️ **Experiment — not for merge.** Posted to share a measured result.
Stacked on #105 (`vv/merge-slot-maps`); the diff here is just the Group
A commit.

## What
"Group A" of #104's map-consolidation idea: merge the two
**internal-rollback** `(address, key)` maps — `storage_changes` (`U256`)
and `paid_changes` (`u32`) — into one `RollbackableMap<(H160, U256),
StorageWriteEntry { value, paid }>`, storing the 52-byte key once
instead of twice. `transient_storage_changes` is deliberately **left
separate** (distinct keyspace — merging it would only add an
always-absent field to every persistent entry).

## Correctness
- 54 lib tests pass (incl. the `storage_changes_*` proptests and the
Boojum trace tests).
- Validated end-to-end on the matter-labs/eravm-airbender-verifier
guest: batch 67912 produces the **identical commitment / proof public
input** as baseline.
- One subtlety handled: `get_storage_changes_after` must *not* filter
`before == after` — a paid update only happens within a `write_storage`
call alongside the value write, and `changes_after` coalesces by key, so
there is no standalone "paid-only" change. (A proptest caught an earlier
wrong filter.)

## Measured saving
On the eravm guest, worst-case 67912 fits at **720 MiB** vs **768 MiB**
with #105 (Group B) alone → **~48 MiB additional** (cumulative B+A ≈
~200 MiB off guest peak).

## Cost / why it's an experiment, not a ready PR
- `get_storage_state()` changes return type to `&BTreeMap<_,
StorageWriteEntry>`, so **every consumer must project `.value`** (1 site
here in `tracing.rs`; 2 in the eravm verifier's `vm_fast`). API ripple.
- It sits on the **SLOAD/SSTORE hot path**, so it needs **shadow-mode
validation** against the legacy VM before it could land.
- ROI is lower than #105: ~⅓ the saving for more surface area and risk.

**Recommendation:** hold unless a concrete memory need remains after
#105 + the guest heap bump. Opening as a draft so the data and approach
are on record.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- re-export `StorageWriteEntry` from the crate root (it's now part of the
  public `get_storage_state()` return type) [P2]
- write_storage: one insert per path instead of two for non-free writes —
  compute the final paid amount, then insert once [P3]
- add a non-zero-cost test (`merged_storage_write_tracks_paid_and_rolls_back`)
  covering prior_paid/prepaid + rollback of the merged entry [P3]
- refresh stale doc comments referencing the old `storage_changes` /
  `committed_reads_at_depth_zero` field names; document StorageWriteEntry fields

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@0xVolosnikov 0xVolosnikov changed the title perf: merge per-slot (address,key) sets into one flags map perf: consolidate WorldDiff (address,key) maps to cut verifier memory Jun 15, 2026
@0xVolosnikov 0xVolosnikov requested a review from Copilot June 15, 2026 18:03

Copilot AI 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.

Pull request overview

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

@0xVolosnikov 0xVolosnikov merged commit 8f12ba0 into vv/memopt-on-popzxc Jun 15, 2026
8 checks passed
@0xVolosnikov 0xVolosnikov deleted the vv/merge-slot-maps branch June 15, 2026 18:07
0xVolosnikov added a commit that referenced this pull request Jul 2, 2026
…#105)

Reduces `WorldDiff` memory on large mainnet batches (consumer:
matter-labs/eravm-airbender-verifier#18) by removing duplicated
`(address, key)` keys across its maps. **Stacked on #104**
(`vv/memopt-on-popzxc`).

> **Scope note:** this PR now contains **both** consolidation steps —
the experimental "Group A" (#106) was merged into this branch, so it's
no longer separate. Both are described below.

## Changes
- **Group B — merge the three membership sets** (`read_storage_slots`,
`written_storage_slots`, `committed_reads_at_depth_zero`) into one
`slot_flags: RollbackableMap<(H160,U256), u8>` of bit flags.
External-rollback semantics preserved; public
`committed_reads_at_depth_zero_iter` kept (now filters the flag).
- **Group A — merge the two internal-rollback write maps**
(`storage_changes: U256` + `paid_changes: u32`) into one
`storage_writes: RollbackableMap<(H160,U256), StorageWriteEntry { value,
paid }>`. `transient_storage_changes` left separate (distinct keyspace).

## ⚠️ Public API change
`WorldDiff::get_storage_state()` now returns `&BTreeMap<_,
StorageWriteEntry>` (was `…, U256>`), and `StorageWriteEntry` is
re-exported from the crate root. **Direct callers must project
`.value`.** The downstream consumer is eravm-airbender-verifier's
`vm_fast` (2 sites) — its vm2 pin bump must land together with that
`.value` projection.

## Correctness
- Rollback groups unchanged (`slot_flags` external; `storage_writes`
internal).
- `write_storage` does a single insert per path; `prepaid` reuses the
prior entry value (no redundant lookup/history).
- **55 lib tests pass**, including the `storage_changes_*` proptests,
the Boojum storage-log trace tests, and a new
`merged_storage_write_tracks_paid_and_rolls_back` covering non-zero
`prepaid` + rollback.

## Measured impact
On the eravm guest, the worst-case production batch (67912) drops from
needing ~920–952 MiB to **fitting at ~720 MiB** (~200 MiB off peak) —
turning a <32 MiB margin into comfortable headroom.

## Review items addressed
P2 (re-export `StorageWriteEntry`), P3 (single insert in
`write_storage`; add non-zero-paid test), and stale doc-comment
field-name refs — all in `1044b47`. P1 (scope) addressed by this
description; the API break is called out above for the coordinated eravm
change.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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