Skip to content

refactor(alloy-op-evm): Phase 0 — externalize the SDM refund inspector seam#21504

Draft
nonsense wants to merge 7 commits into
developfrom
ae/sdm/phase0-refund-inspector-seam
Draft

refactor(alloy-op-evm): Phase 0 — externalize the SDM refund inspector seam#21504
nonsense wants to merge 7 commits into
developfrom
ae/sdm/phase0-refund-inspector-seam

Conversation

@nonsense

Copy link
Copy Markdown
Contributor

Summary

Phase 0 of the SDM → optimism-premium extraction: introduce a refund-inspector seam in the monorepo so which PostExecRefundInspector runs becomes a construction-time choice, while keeping behavior byte-for-byte identical. Stacked on #21502 (pre-work); review the diff against that branch.

This is the riskiest consensus-adjacent refactor in the plan — it ships zero behavior change and is proven so by the full workspace suite + two independent reviews.

What changed

  • New seam (alloy-op-evm/src/post_exec/refund.rs): PostExecRefundInspector (CTX-free; Snapshot, begin_tx, note_account_touch, finish_tx -> u64, snapshot, restore) + NoopRefundInspector. SDMWarmingInspector implements it.
  • Generic R: PostExecCompositeInspector<I><I, R = SDMWarmingInspector>; OpEvm gains a trailing R = SDMWarmingInspector param, driven through the trait. OpEvmFactory stays non-generic (uses the default), so every monorepo node still installs SDMWarmingInspector exactly as before.
  • finish_tx -> u64 is the cross-repo contract. Producer warming-attribution telemetry (WarmingRefundEvent/warming_events_by_tx, PostExecAdjustment.warming_events, the post-exec-replay refund_breakdown) is dropped — non-consensus; it moves to premium in Phase 1.
  • Executor tests that asserted intrinsic-warmth/fee-vault exclusion via attribution were re-pointed at the consensus-visible aggregate refund.
  • §6.1 CREATE-address staleness investigated → not a bug: revm's CreateInputs::created_address memoizes the canonical (pre-bump-nonce) address before the inspector hook runs, so the inspector's nonce is ignored. Added a regression guard + clarifying comments.

Plan deviation (important)

The plan's §2.1 mechanism ("collapse to the single I slot, no new type param") does not compile against alloy-evm 0.36: BlockExecutorFactory::Executor<DB,I>: BlockExecutor for all I forces OpEvm<DB,I>: PostExecEvm for every inspector, which is exactly why an always-present refund inspector (the composite) is required. Hence the factory-fixed R type param. A factory generic over R hits an HRTB-over-DB wall, so premium will ship its own factory fixing its R.

Deferred to Phase 1

Associated-Snapshot opacity (kept the Snapshot = WarmingState bound) and the Noop-for-non-producers split.

Testing

  • Full workspace unit suite: 2527 passed, 11 skipped.
  • cargo check --workspace --all-features --all-targets clean; clippy clean on the changed crates; no-std OK.
  • Producer→verifier roundtrip + settlement/receipt/state-root parity all green.
  • Two independent reviews (security + adversarial correctness): APPROVE — consensus surface provably unchanged; dropped data is telemetry only.

Follow-ups (tracked, not in this PR)

  • §6.2: decide the gas_refund_entries length/DA cap (block-validity).
  • Phase 1: move SDMWarmingInspector to premium, flip default R to Noop, lift the Snapshot=WarmingState bound to an associated Snapshot.

🤖 Generated with Claude Code

@nonsense nonsense force-pushed the ae/sdm/prework-consensus-surface branch 2 times, most recently from f5d767f to 72be2f3 Compare June 23, 2026 08:26
Reject Verify blocks that claim refunds without the trailing post-exec tx, and snapshot PostExecMode once per payload build so EVM setup and 0x7D appending agree.
@nonsense nonsense force-pushed the ae/sdm/prework-consensus-surface branch from 72be2f3 to 85837ca Compare June 23, 2026 09:20
nonsense and others added 6 commits June 23, 2026 12:38
The reth-codec opt-in change (#21483) added reth-optimism-primitives as a
dependency of reth-optimism-post-exec-replay but only updated the main rust
workspace lockfile. The op-rbuilder and rollup-boost workspaces depend on
that crate via path, so their Cargo.lock files went stale and the Docker
builds (which run `cargo chef cook --locked`) failed with "cannot update
the lock file because --locked was passed".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Verify-mode block whose normal txs apply the claimed SDM refunds but
which never carries the trailing 0x7D post-exec tx passed finish():
per-tx settlement drains the verifier `remaining` map as the refunded
txs commit, so an absent 0x7D was invisible to the unconsumed-entries
check. finish() now also rejects `Verifying { saw_post_exec_tx: false }`
via the new PostExecState::missing_post_exec_tx().

Pre-work for the SDM premium extraction (consensus-surface stabilization).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…CTOU

build() read the runtime-mutable SDM production opt-in twice — once in
block_builder() (mode selection) and again when deciding to append the
trailing 0x7D. An admin-RPC toggle between the reads could yield a block
with refunded state but no 0x7D (or vice versa). Resolve post_exec_mode()
once and thread it through the new block_builder_with_mode(); derive the
append decision from the same snapshot via matches!(mode, Produce).

Pre-work for the SDM premium extraction (consensus-surface stabilization).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ector

Introduce the producer/consensus boundary trait for SDM post-exec refunds:
`PostExecRefundInspector<CTX>: Inspector<CTX>` with an opaque block-scoped
`Snapshot` and a per-tx `finish_tx -> u64` contract — the entire cross-repo
surface a sequencer (optimism-premium, later) implements. Add
`NoopRefundInspector` (the non-producing default) and make
`SDMWarmingInspector` implement the trait.

Purely additive; no behavior change, not yet wired into EVM construction.
First step of the Phase 0 refund-inspector seam (SDM premium extraction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… generic R

Phase 0 of the SDM premium extraction: make which PostExecRefundInspector runs
a construction-time choice instead of a hardcoded SDMWarmingInspector, with no
behavior change.

- PostExecCompositeInspector<I> -> <I, R = SDMWarmingInspector>; the embedded
  refund inspector is now the generic R, driven through the
  PostExecRefundInspector trait rather than calling SDMWarmingInspector directly.
- OpEvm gains a trailing R = SDMWarmingInspector param; OpEvmFactory stays
  non-generic and uses the default, so every monorepo node still installs
  SDMWarmingInspector exactly as before. (A factory generic over R hits an
  HRTB-over-DB wall against alloy-evm's BlockExecutorFactory bound; premium will
  ship its own factory fixing its R.)
- The per-tx hand-off narrows to finish_tx -> u64, the cross-repo contract.
  Producer warming-attribution telemetry (WarmingRefundEvent/warming_events_by_tx
  on the executor, PostExecAdjustment.warming_events, the post-exec-replay
  refund_breakdown) is dropped: non-consensus, it moves to premium in Phase 1.
- Executor tests that asserted intrinsic-warmth / fee-vault exclusion via
  attribution events now assert the consensus-visible aggregate refund.

Snapshot opacity (associated Snapshot) and the Noop-for-non-producers split are
deferred to Phase 1, so the EVM keeps WarmingState concrete via a
PostExecRefundInspector<Snapshot = WarmingState> bound. Behavior-preserving: full
workspace unit suite green (2527 passed); the consensus surface (Verify,
settlement, 0x7D refund amounts, receipts, state root) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ess cache

Investigated the SDM warming inspector's CREATE-address derivation (plan §6.1,
"CREATE-address staleness"). It does NOT reproduce against the pinned revm:
`CreateInputs::created_address` memoizes its result via `OnceCell`, computed by
the canonical create frame from the pre-bump creator nonce before the inspector
`create` hook runs — so the nonce the inspector passes is ignored and the
recorded address is always correct. A mutation forcing `nonce + 7` is a no-op.

Add a regression test that a contract created by one tx is warmed for a later
tx that cold-accesses it (and earns its rebate), and document the memoization
in the `create` hook so the journal-nonce read isn't misread as load-bearing.
No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nonsense nonsense force-pushed the ae/sdm/phase0-refund-inspector-seam branch from 484b065 to cefcd02 Compare June 23, 2026 09:52
Base automatically changed from ae/sdm/prework-consensus-surface to develop June 23, 2026 12:01
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