Skip to content

perf: remove beneficiary commit barriers - #119

Merged
AshinGau merged 1 commit into
Galxe:mainfrom
AshinGau:main
Aug 4, 2026
Merged

perf: remove beneficiary commit barriers#119
AshinGau merged 1 commit into
Galxe:mainfrom
AshinGau:main

Conversation

@AshinGau

@AshinGau AshinGau commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Closes Galxe/gravity-audit#773.
Closes Galxe/gravity-audit#775.

Summary

This PR removes two commit-prefix barriers from Grevm's speculative execution path while preserving upstream revm semantics:

  • protocol beneficiary rewards are represented by an incarnation-aware, block-scoped beneficiary account history instead of forcing transactions to wait for the previous ordered commit;
  • account deletion, creation, and storage clearing are represented by the normal MV-memory location StorageReset(address) instead of a special self-destruct path that can only be resolved from committed state.

It also replaces the former CacheDB abstraction with IncarnationDb, which owns the complete database lifecycle of one Block-STM transaction incarnation.

The optimization is intentionally narrow: incremental balance handling applies only to the protocol beneficiary reward. Ordinary value transfers, sender and recipient balances, and transaction nonce semantics remain unchanged.

Motivation

Every successful transaction may reward the block beneficiary. Treating that reward as an ordinary absolute account write creates an almost block-wide dependency chain. The previous workaround skipped beneficiary MV-memory writes, but explicit beneficiary reads could then need the committed prefix to obtain an authoritative balance.

Account deletion had a similar problem. A special SelfDestructed marker required later account or storage reads to wait for commit before continuing safely. That path also mixed together fork-specific SELFDESTRUCT behavior, EIP-161 empty-account clearing, creation, storage lifetime, and balance ordering.

Finally, the old execution interface exposed a reusable database through several independent take_* calls plus a separate MV-memory update. This made the incarnation boundary implicit and allowed dependency metadata and write publication to drift apart.

Design

1. Beneficiary reward history

The execution handler remains the single source of truth for reward calculation and disposition:

  • when fee charging is disabled, no beneficiary reward is produced;
  • a zero reward still uses the upstream revm hook, preserving load, touch, and EIP-161 behavior;
  • if the beneficiary is already present in the transaction journal, the reward is applied immediately through upstream revm, preserving journal order with explicit beneficiary writes and SELFDESTRUCT;
  • otherwise, a positive reward is returned as a DeferredBeneficiaryReward.

A deferred reward contains only the non-zero reward amount. The beneficiary address is a block invariant already owned by Beneficiary and OrderedCommitter, so it is not duplicated in every speculative result.

Each transaction owns one preallocated entry in BeneficiaryHistory:

EntryState {
    incarnation,
    value: Estimate | Exact(BeneficiaryEffect),
}

BeneficiaryEffect =
    Unchanged
  | Reward(DeferredBeneficiaryReward)
  | Snapshot(Option<AccountInfo>)
  • Reward applies a protocol reward to the preceding beneficiary value.
  • Snapshot(Some(account)) records an explicit finalized beneficiary update and terminates the preceding reward chain.
  • Snapshot(None) records an actual deletion or finalized empty-account clearing.
  • Unchanged records an exact no-op, allowing later readers to distinguish it from an unresolved estimate.

Reads walk backward to the newest snapshot or the immutable block-start anchor, then apply contributing rewards in transaction order. Validation compares the complete contributing incarnation chain, not only the newest writer.

flowchart TD
    A[Execute transaction incarnation] --> B{Beneficiary reward path}
    B -->|Fee charging disabled| C[No reward]
    B -->|Zero reward| D[Apply upstream revm hook]
    B -->|Beneficiary already in journal| D
    B -->|Positive reward and not journaled| E[Return DeferredBeneficiaryReward]

    C --> F[Finalize EVM state]
    D --> F
    E --> F
    F --> G[Publish ordinary MV-memory writes]
    G --> H[Publish exact BeneficiaryHistory effect]
    H --> I[Validate complete origin chain]
    I --> J[Ordered commit]
    J --> K[Fold deferred reward into EVM state]
    K --> L[Commit transaction state once]
Loading

Publication remains ordered: ordinary MV-memory writes become visible before the beneficiary entry becomes exact. Failed, blocked, or conflicting incarnations publish an estimate, and stale executions or validations cannot overwrite a newer incarnation.

At ordered commit, the reward is checked-added to the authoritative beneficiary account and inserted into the same finalized EvmState. The transaction state is then committed once, preserving upstream overflow and account-materialization behavior without a separate balance-increment commit.

2. Account lifecycle and storage reset

FinalizedAccount centralizes the lifecycle classification already produced by revm:

  • Unchanged: merely loaded, with no consensus-visible write;
  • Deleted: actual SELFDESTRUCT or finalized EIP-161 empty-account removal;
  • Created: a newly created account whose previous storage is cleared;
  • Updated: an update to an existing account.

IncarnationDb publishes StorageReset(address) for both deletion and creation. Storage reads independently resolve:

  1. the newest preceding slot write; and
  2. the newest preceding storage reset.
flowchart LR
    A[Finalized revm account] --> B{Lifecycle}
    B -->|Deleted| R[Publish StorageReset]
    B -->|Created| R
    B -->|Updated EIP-7702 delegation| C[Publish Code without reset]

    S[Read address and slot] --> W[Latest preceding slot write]
    S --> X[Latest preceding StorageReset]
    W --> D{Which version is newer?}
    X --> D
    D -->|Slot write at or after reset| V[Return slot value]
    D -->|Reset is newer| Z[Return zero]
    D -->|Neither exists| DB[Read backing database]
Loading

Both locations enter the read set, so a newly discovered earlier reset invalidates a speculative reader through normal MV-memory validation.

This delegates hardfork semantics to finalized revm state:

  • pre-Cancun deletion clears account storage;
  • post-Cancun SELFDESTRUCT of a pre-existing account remains a balance transfer;
  • creation followed by SELFDESTRUCT in the same transaction remains an actual deletion;
  • EIP-7702 delegation changes code without clearing storage.

Beneficiary storage stays on this generic MV-memory path and is independent from beneficiary reward history. A storage-only access therefore does not wait for unresolved beneficiary balance rewards.

3. Explicit incarnation lifecycle

The former CacheDB is now IncarnationDb: a reusable revm::Database adapter for the currently executing transaction incarnation.

begin_incarnation(version)
    -> execute and finalize EVM
    -> finish_incarnation(state) | discard_incarnation()
    -> IncarnationExecution { result, IncarnationAccesses }

IncarnationDb owns:

  • reads from preceding MV-memory versions, beneficiary history, and the backing database;
  • the current read set and account snapshots;
  • blockers discovered from estimated predecessors;
  • beneficiary-specific blocking metadata;
  • publication of the incarnation's write set.

finish_incarnation derives the MV-memory estimate flag directly from the blocker set, publishes writes, and returns all IncarnationAccesses atomically. discard_incarnation publishes no EVM writes but preserves discovered blockers and retains scratch allocation capacity for reuse.

GrevmExecutor now encapsulates the entire begin/execute/finish-or-discard lifecycle. The scheduler no longer obtains a mutable database handle or sequences multiple take_* calls.

Component boundaries

Component Responsibility
account.rs Classify finalized revm account lifecycle
beneficiary/reward.rs Calculate rewards and preserve immediate/deferred revm semantics
beneficiary/history.rs Own beneficiary effects, retries, reads, and full-origin validation
beneficiary.rs Bind the block beneficiary, its history, and speculative reward output
incarnation_db.rs Resolve incarnation-visible state and publish MV-memory writes/StorageReset
scheduler/executor.rs Own the complete incarnation execution lifecycle
scheduler/ordered_commit.rs Validate nonce, merge a deferred reward, and commit state once

Custom precompile contract

This PR does not turn arbitrary shared stateful closures into rollback-aware precompiles. Custom precompiles supplied to the parallel scheduler must remain concurrent and retry safe:

  • consensus-visible writes must go through the EVM journal;
  • discarded speculative calls must not leave externally visible mutable state;
  • shared closures must not retain out-of-band consensus state across retries;
  • state reads must follow the documented journal/database access contract.

Under this integration invariant, shallow cloning of a custom precompile is intentional and is not a missing rollback mechanism.

Correctness coverage

The test suite covers:

  • reward calculation against the upstream hook across pre-London, London, and reservoir-gas rules;
  • fee-disabled and zero-reward touch behavior;
  • absent beneficiary materialization and checked-add overflow;
  • beneficiary estimates, exact no-ops, snapshots, retries, stale incarnations, and full-origin validation;
  • incarnation finish/discard behavior and estimate propagation;
  • beneficiary storage reads without a balance dependency;
  • EIP-161 empty-account clearing;
  • pre- and post-Cancun SELFDESTRUCT;
  • same-transaction and prior-transaction creation;
  • self-targeted and reverted SELFDESTRUCT;
  • beneficiary deletion/reward ordering;
  • EIP-7702 code changes preserving storage.

Validation

  • cargo test --all-targets --all-features
  • cargo clippy --all-targets --all-features -- -D warnings
  • cross-fork differential tests against sequential upstream revm for Frontier, Spurious Dragon, Shanghai, Cancun, Prague, and Amsterdam
  • 100 consecutive Prague mainnet blocks (23,352,851..=23,352,950) replayed successfully with parallel results and bundle state matching sequential revm

In that 100-block replay sample, aggregate in-memory execution and bundle extraction took 1.118 s with sequential revm and 0.679 s with Grevm, approximately 1.65x faster. This is an illustrative replay result, not a benchmark guarantee.

@AshinGau
AshinGau force-pushed the main branch 2 times, most recently from 6d696e7 to 62742b1 Compare August 1, 2026 03:22
@AshinGau
AshinGau merged commit 020238a into Galxe:main Aug 4, 2026
2 checks passed
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.

1 participant