Skip to content

feat(alloy-op-evm): post-exec block executor and SDM warming inspector#20213

Open
nonsense wants to merge 2 commits intodevelopfrom
nonsense/sdm/post-exec-block-executor
Open

feat(alloy-op-evm): post-exec block executor and SDM warming inspector#20213
nonsense wants to merge 2 commits intodevelopfrom
nonsense/sdm/post-exec-block-executor

Conversation

@nonsense
Copy link
Copy Markdown
Contributor

@nonsense nonsense commented Apr 21, 2026

Fixes: #20174
Fixes: #20178
Fixes: #20179


This PR adds the SDM post-exec transaction plumbing across op-alloy, alloy-op-evm, op-reth, Kona proof execution, and custom-node extension points.

The branch introduces a OP transaction type 0x7D (TxPostExec) that carries a versioned PostExecPayload with per-transaction gas-refund entries. During payload production, the block executor can compute SDM warming refunds and append a final post-exec transaction. During replay, the executor can parse, verify, and apply an embedded post-exec payload so gas accounting, receipts, and fee settlement match the producer.

SDM is currently not scheduled. It does not activate with Jovian or Karst. Normal op-reth/Kona execution keeps SDM disabled by default. op-reth has an explicit --rollup.sdm-enabled integration-test override to exercise production, replay verification, and RPC receipt conversion paths.

Related: #20216

Main changes

  • Add OP post-exec transaction type 0x7D and payload/validation helpers in op-alloy.
  • Add SDM warming tracking via a composite inspector in alloy-op-evm - PostExecCompositeInspector<I>
  • Add block executor modes: PostExecMode::Disabled, PostExecMode::Produce, PostExecMode::Verify.
  • Apply canonical gas accounting, receipts, cumulative gas, and fee settlement when refunds are used.
  • Append post-exec txs in the payload builder when the explicit SDM override is enabled and nonzero refunds exist.
  • Parse/verify embedded post-exec payloads in op-reth replay, engine payload replay, RPC receipts, and Kona proof execution plumbing.
  • Add custom EVM / custom transaction extension traits and update the custom-node example.
  • Keep SDM disabled by default, including after Jovian/Karst.

Review checklist

1. Activation / rollout safety

  • Confirm SDM is not tied to Jovian or Karst.
    • kona::RollupConfig::is_sdm_active returns false.
    • op-reth::OpEvmConfig::is_sdm_active_at_timestamp returns only sdm_enabled.
    • OpPayloadBuilderCtx::sdm_production_enabled returns only builder_config.sdm_enabled.
  • Confirm default constructors keep SDM off.
    • OpEvmConfig::new / optimism: sdm_enabled = false.
    • OpBuilderConfig::new: sdm_enabled = false.
    • OpReceiptConverter::new: sdm_enabled = false.
  • Confirm --rollup.sdm-enabled is documented as integration-test/experimental only.
  • Confirm enabling the override is threaded consistently to:
    • payload production;
    • block replay / engine payload replay;
    • RPC receipt conversion.
  • Confirm normal nodes reject any block containing 0x7D while SDM is disabled.

2. New op-alloy consensus symbols

Review file: rust/op-alloy/crates/consensus/src/post_exec.rs

  • POST_EXEC_PAYLOAD_VERSION
    • Unknown versions rejected by payload decode.
  • SDMGasEntry { index, gas_refund }
    • index semantics are block transaction index.
    • gas_refund must be nonzero in verifier payloads.
  • PostExecPayload { version, block_number, gas_refund_entries }
    • block_number anchors hash/replay to containing block. It there for the purpose of uniqueness.
    • RLP layout is stable and covered by tests.
  • ParsedPostExecPayload { tx_index, payload }
    • tx_index is the index of the post-exec tx.
  • PostExecPayloadValidationError
    • Unexpected when disabled.
    • Multiple post-exec txs.
    • Not final tx.
    • Block-number mismatch.
  • parse_post_exec_payload_from_transactions(...)
    • Single shared parser used by replay/RPC/Kona paths.
    • Takes explicit sdm_active boolean from caller.
  • TxPostExec
    • Confirm tx behavior is correct:
      • zero gas limit;
      • zero value;
      • zero nonce;
      • canonical zero signer;
      • input is canonical payload RLP;
      • hash is deterministic over type byte + payload.
  • build_post_exec_tx(...)
    • Uses current payload version.
    • Preserves block number and entries.

Related transaction/receipt envelope changes:

  • OpTransaction::as_post_exec helper.
  • OpTxEnvelope::PostExec variant.
  • OpTypedTransaction::PostExec variant.
  • OpTxType::PostExec variant.
  • OpReceiptEnvelope / receipt handling for post-exec receipts.
  • Network/pool handling rejects or excludes post-exec txs from unsupported paths.

3. New alloy-op-evm post-exec symbols

Review files:

  • rust/alloy-op-evm/src/post_exec/mod.rs
  • rust/alloy-op-evm/src/post_exec/inspector.rs
  • rust/alloy-op-evm/src/lib.rs

New extension symbols:

  • PostExecTxKind
    • Normal may claim refunds.
    • Deposit warms but cannot claim refunds.
    • PostExec cannot claim refunds.
  • PostExecTxContext
    • Correctly carries tx index/kind into tracking.
  • PostExecExecutedTx
    • Correctly exposes total refund for the last tracked tx.
  • PostExecEvm trait
    • Minimal EVM-facing hooks: begin tracking / take result.
  • PostExecEvmFactoryHooks
    • Check generic associated-type bounds are necessary and stable.
  • PostExecEvmAdapter
    • Does not change ordinary EVM behavior except exposing post-exec hooks.
  • PostExecEvmFactoryAdapter
    • Correctly wraps custom factories and forwards hooks.
  • PostExecExecutorExt
    • Lets payload builder drain produced entries without depending on concrete executor.

Inspector symbols:

  • SDMWarmingInspector
    • Tracks block-level warmed accounts/storage.
    • Initializes intrinsic/access-list/authorization warmth before opcode observations.
    • Deposits warm subsequent txs but never claim refunds.
    • Post-exec txs never claim refunds.
  • PostExecCompositeInspector<I>
    • Preserves user inspector behavior.
    • Does not let SDM inspector synthesize call/create outcomes.
  • Refund constants/semantics:
    • account re-warm refund;
    • SLOAD slot re-warm refund;
    • SSTORE slot re-warm refund.
  • Protocol-level touches noted by OpEvm:
    • L1 fee recipient;
    • base fee recipient;
    • operator fee recipient after Isthmus.
  • Fast path remains intact:
    • No inspector execution unless user inspection or post-exec tracking is active.

4. New alloy-op-evm block executor symbols / behavior

Review file: rust/alloy-op-evm/src/block/mod.rs

  • PostExecMode
    • Disabled rejects 0x7D.
    • Produce collects entries and accepts builder-created post-exec tx.
    • Verify(PostExecPayload) verifies embedded payload.
  • PostExecState
    • Tracks produced entries.
    • Tracks verifier remaining entries.
    • Tracks invalid initialization reasons.
    • Tracks whether post-exec tx was seen.
  • PostExecAdjustment
    • Fee/gas settlement details are correctly applied.
  • OpBlockExecutionError::InvalidPostExecPayload(String)
    • Error messages identify invalid payload cases.
  • OpTxResult raw/canonical gas fields
    • Raw gas used remains EVM raw result.
    • Canonical gas used drives receipts/block gas.
  • Disabled mode rejects post-exec txs.
  • Produce mode:
    • starts tracking before each normal/deposit tx;
    • rejects refund > raw gas used;
    • records only nonzero ordinary tx refunds;
    • appends post-exec tx only via payload builder.
  • Verify mode:
    • rejects duplicate payload indices;
    • rejects zero refund entries;
    • rejects deposits/post-exec tx as refund targets;
    • rejects refund > raw gas used;
    • rejects unconsumed entries at finish;
    • validates actual tx payload equals expected payload.
  • Post-exec tx short-circuits before state changes and uses zero gas.
  • Post-exec txs do not count toward Jovian DA footprint / blob gas used.
  • Fee settlement debits/credits are balanced and underflow is fatal.
  • Receipts use canonical cumulative gas.

5. New op-reth EVM symbols

Review files:

  • rust/op-reth/crates/evm/src/lib.rs

  • rust/op-reth/crates/evm/src/post_exec_ext.rs

  • OpEvmConfig::sdm_enabled

    • Hidden field defaults false.
    • Cloned correctly.
  • OpEvmConfig::with_sdm_enabled(...)

    • Integration-test override only.
  • OpEvmConfig::is_sdm_active_at_timestamp(...) -> bool

    • const fn.
    • Ignores timestamp and returns override.
  • ConfigureEvm::context_for_block

    • Uses shared parser.
    • Passes explicit override as sdm_active.
    • Maps parser errors to EIP1559ParamError::InvalidPostExecPayload.
  • ConfigureEngineEvm::context_for_payload

    • Decodes txs to parse post-exec payload.
    • Uses shared parser and override.
    • Sets PostExecMode::Verify only when enabled and payload valid.
  • ConfigurePostExecEvm trait

    • Explicit constructors for block replay and next-block production.
    • Generic bounds are not overly restrictive.
  • PostExecEvmFactoryAdapter impl path

    • Custom factory support mirrors default OP EVM support.

6. op-reth payload builder symbols

Review files:

  • rust/op-reth/crates/payload/src/config.rs

  • rust/op-reth/crates/payload/src/builder.rs

  • rust/op-reth/crates/primitives/src/transaction/mod.rs

  • OpBuilderConfig::sdm_enabled

    • Defaults false.
    • new_with_sdm only explicitly enables it.
  • OpPayloadBuilderCtx::sdm_production_enabled()

    • Returns only explicit override.
  • build_post_exec_recovered_tx(...)

    • Builds recovered synthetic tx with canonical signer.
  • try_include_post_exec_tx(...)

    • No-op for empty entries.
    • Aborts payload build if post-exec tx execution fails.
  • Payload construction order:

    • execute sequencer txs;
    • execute best txs;
    • drain entries;
    • append post-exec tx if needed;
    • seal/finalize.
  • BuildPostExecTransaction trait

    • Required methods are minimal.
    • Default OP signed tx implementation is correct.
    • Custom tx enums can implement it without leaking OP-specific concrete types.

7. op-reth node / RPC symbols

Review files:

  • rust/op-reth/crates/node/src/args.rs
  • rust/op-reth/crates/node/src/node.rs
  • rust/op-reth/crates/rpc/src/eth/mod.rs
  • rust/op-reth/crates/rpc/src/eth/receipt.rs
  • rust/op-reth/crates/rpc/src/error.rs

Node config:

  • RollupArgs::sdm_enabled
    • CLI name: --rollup.sdm-enabled.
    • Help text says experimental/integration-test-only.
  • OpExecutorBuilder::sdm_enabled and with_sdm_enabled.
  • OpPayloadBuilder::sdm_enabled and with_sdm_enabled.
  • OpAddOnsBuilder::sdm_enabled and with_sdm_enabled.
  • Wiring from RollupArgs reaches all three:
    • executor/EVM config;
    • payload builder;
    • RPC receipt converter.

RPC:

  • OpEthApiBuilder::sdm_enabled and with_sdm_enabled.
  • OpReceiptConverter::sdm_enabled and with_sdm_enabled.
  • Receipt conversion uses shared parser with override.
  • op_gas_refund is filled only from validated payload entries.
  • OpEthApiError::InvalidPostExecPayload maps structural parser errors cleanly.
  • Debug/witness paths remain consistent with updated EVM config behavior.

8. Kona / proof changes

Review files:

  • rust/kona/crates/protocol/genesis/src/rollup.rs

  • rust/kona/crates/proof/executor/src/builder/core.rs

  • rust/kona/crates/proof/executor/src/errors.rs

  • Kona client/proof call-site generic-bound updates.

  • RollupConfig::is_sdm_active(...) always returns false.

  • Unit test proves SDM remains disabled after Jovian/Karst.

  • Stateless builder uses shared parser.

  • Parser input comes from rollup config, so post-exec txs are rejected by default.

  • ExecutorError::InvalidPostExecPayload(String) maps parser details into proof executor errors.

  • New generic bounds needed for post-exec-aware EVM factory are justified and minimal.

9. Custom-node / downstream extension review

Review files:

  • rust/op-reth/examples/custom-node/src/evm/alloy.rs

  • rust/op-reth/examples/custom-node/src/evm/config.rs

  • rust/op-reth/examples/custom-node/src/evm/executor.rs

  • rust/op-reth/examples/custom-node/src/primitives/tx.rs

  • rust/op-reth/crates/node/tests/it/builder.rs

  • Custom EVM wrapper forwards PostExecEvm hooks correctly.

  • Custom EVM config exposes explicit post-exec constructors.

  • Custom executor implements/delegates PostExecExecutorExt.

  • Custom transaction enum implements BuildPostExecTransaction.

  • Uni custom precompile test uses PostExecEvmFactoryAdapter correctly.

  • Added generic bounds do not make custom-node extension ergonomics too difficult.

10. Test / invariant checklist

  • Unit tests cover TxPostExec encoding/decoding/hash behavior.
  • Unit tests cover parser errors:
    • disabled/unexpected tx;
    • multiple txs;
    • non-final tx;
    • block-number mismatch.
  • Unit tests cover verifier payload errors:
    • duplicate entry;
    • zero refund;
    • deposit target;
    • post-exec target;
    • refund > raw gas;
    • unconsumed entries.
  • Unit tests cover disabled-mode rejection.
  • Unit tests cover empty refund set skipping post-exec tx production.
  • Unit tests cover post-exec tx production happy path and failure path.
  • RPC test covers extracting op_gas_refund from embedded payload with override enabled.
  • Kona test covers SDM disabled after Jovian/Karst.
  • Clippy/doc tests pass after activation-disable changes.

High-risk areas to review extra carefully

  1. Consensus encoding stability: RLP payload layout, typed tx hash, envelope variants.
  2. Gas accounting: raw vs canonical gas, cumulative gas, block gas used.
  3. State settlement: sender/recipient debits/credits and underflow handling.
  4. Verifier strictness: no impossible refund payload should be accepted.
  5. Activation safety: no Jovian/Karst path should accidentally accept/produce 0x7D without the explicit override.
  6. Custom extensibility: new traits should be minimal and not overconstrain downstream custom EVMs/tx types.
  7. Fast path performance: SDM-disabled execution should not force inspector mode.

TODO / questions to answer and review prior to merging:

  • Behaviour: confirm that there is no change in behavior on develop with this PR.
  • Behaviour: consensus activation (derived from chain spec / hardfork / rollup config) vs local flag (sdm_enabled); SDM should be disabled by default at every layer within this PR.
  • SDM activation: confirm that sdm_enabled is propagated correctly across the codebase.
  • SDM activation: define when SDM should be active (consensus)
  • Sanity-check: should we keep sanity check for verifiers who re-run the block and reject it unless the embedded payload matches what they independently computed from the sequencer
  • Policy refactor: support for multiple policies and structure - now or in a future PR?

@nonsense nonsense requested a review from einar-oplabs April 21, 2026 14:03
@nonsense nonsense force-pushed the nonsense/sdm/post-exec-block-executor branch from d88b6a5 to 4fafc44 Compare April 22, 2026 11:23
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 65.00308% with 568 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.6%. Comparing base (42f5117) to head (ffc7072).
⚠️ Report is 7 commits behind head on develop.

Files with missing lines Patch % Lines
rust/alloy-op-evm/src/block/mod.rs 76.2% 151 Missing ⚠️
rust/alloy-op-evm/src/post_exec/inspector.rs 64.8% 118 Missing ⚠️
rust/alloy-op-evm/src/post_exec/mod.rs 0.0% 80 Missing ⚠️
rust/op-reth/crates/evm/src/post_exec_ext.rs 33.6% 73 Missing ⚠️
rust/alloy-op-evm/src/lib.rs 46.6% 56 Missing ⚠️
rust/op-alloy/crates/consensus/src/post_exec.rs 63.6% 16 Missing ⚠️
rust/op-reth/crates/payload/src/builder.rs 87.1% 13 Missing ⚠️
rust/kona/bin/client/src/fpvm_evm/factory.rs 0.0% 12 Missing ⚠️
rust/op-reth/crates/evm/src/lib.rs 82.8% 12 Missing ⚠️
rust/op-reth/crates/node/src/node.rs 77.2% 10 Missing ⚠️
... and 8 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #20213      +/-   ##
===========================================
+ Coverage     76.1%    76.6%    +0.5%     
===========================================
  Files          184      512     +328     
  Lines        10551    67618   +57067     
===========================================
+ Hits          8035    51862   +43827     
- Misses        2372    15756   +13384     
+ Partials       144        0     -144     
Flag Coverage Δ
cannon-go-tests-64 ?
contracts-bedrock-tests ?
unit 76.6% <65.0%> (?)

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

Files with missing lines Coverage Δ
...ust/kona/crates/proof/executor/src/builder/core.rs 89.8% <100.0%> (ø)
rust/kona/crates/proof/executor/src/errors.rs 100.0% <ø> (ø)
...na/crates/proof/proof-interop/src/consolidation.rs 0.0% <ø> (ø)
rust/kona/crates/proof/proof/src/executor.rs 0.0% <ø> (ø)
rust/kona/crates/protocol/genesis/src/rollup.rs 95.0% <100.0%> (ø)
rust/op-alloy/crates/consensus/src/eip1559.rs 99.1% <ø> (ø)
...op-alloy/crates/consensus/src/receipts/envelope.rs 55.3% <ø> (ø)
...alloy/crates/consensus/src/transaction/envelope.rs 45.4% <ø> (ø)
...op-alloy/crates/consensus/src/transaction/typed.rs 8.7% <ø> (ø)
rust/op-reth/crates/node/src/args.rs 100.0% <100.0%> (ø)
... and 22 more

... and 664 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.

@nonsense nonsense force-pushed the nonsense/sdm/post-exec-block-executor branch 2 times, most recently from 37f0705 to 052c1d6 Compare April 22, 2026 13:42
@nonsense nonsense marked this pull request as ready for review April 22, 2026 13:43
@nonsense nonsense requested a review from a team as a code owner April 22, 2026 13:43
Comment thread rust/alloy-op-evm/src/block/mod.rs Outdated
/// Verify canonical gas accounting using an post-exec payload embedded in the block.
Verify(PostExecPayload),
/// An post-exec tx was present but invalid, so block execution must fail.
Invalid,
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.

Question to self: Is this enum shared between sequencer and verifiers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is.

/// Canonical gas adjustment applied to a transaction when a post-exec refund reduces its gas
/// cost below the raw EVM result.
#[derive(Debug, Default, Clone)]
pub struct PostExecAdjustment {
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.

Question to sync review: How are all of these related?

Comment thread rust/alloy-op-evm/src/block/mod.rs Outdated
/// Begin post-exec tracking for the next transaction.
pub begin_post_exec_tx: fn(&mut Evm, PostExecTxContext),
/// Extractor for the most recent transaction's exact warming result.
pub take_last_post_exec_tx_result: fn(&mut Evm) -> PostExecExecutedTx,
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.

These functions seem to me be outside of a transaction result. I am also not sure why they are functions instead of just PostExecTxContext and PostExecExecutedTx.

Comment thread rust/alloy-op-evm/src/block/mod.rs Outdated
/// Creates a new [`OpBlockExecutor`].
pub fn new(evm: E, ctx: OpBlockExecutionCtx, spec: Spec, receipt_builder: R) -> Self {
let (post_exec_mode, post_exec_verify_entries, post_exec_invalid_reason) =
Self::init_post_exec_state(ctx.post_exec_mode.clone());
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.

Could probably take a ref.

Comment thread rust/alloy-op-evm/src/block/mod.rs Outdated
/// Configure how the executor should read the most recent inspector-backed post-exec result.
pub fn with_post_exec_result(
mut self,
take_last_post_exec_tx_result: fn(&mut E) -> PostExecExecutedTx,
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.

Why not just define a method directly instead of first assigning it to a field?

pub fn take_last_post_exec_tx_result(&self, &mut E) -> PostExecExecutedTx

Comment thread rust/op-reth/crates/node/src/node.rs Outdated
Comment thread rust/op-reth/crates/node/tests/it/builder.rs Outdated
Comment thread rust/op-reth/crates/node/tests/it/builder.rs
//! still demonstrates the rest of the custom-node surface (custom primitives, executor, engine
//! API, engine validator, and RPC).
//!
//! Downstream forks that previously used `OpPayloadBuilder` with their own `_TX` type will need
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.

Could we call the type parameter Transaction or TransactionType and make it default to OpTransactionSigned?

Comment thread rust/op-reth/examples/custom-node/src/lib.rs Outdated
@nonsense nonsense force-pushed the nonsense/sdm/post-exec-block-executor branch 2 times, most recently from 042cd79 to 513dbda Compare April 23, 2026 16:46
@nonsense nonsense requested a review from einar-oplabs April 23, 2026 16:55
@nonsense nonsense force-pushed the nonsense/sdm/post-exec-block-executor branch from 45bec29 to 040f652 Compare April 24, 2026 08:47
Add the canonical post-exec block executor, along with the first
feature riding on it — SDM (Sequencer-Defined Metering) block-level
warming refunds, delivered by the SDMWarmingInspector.

- OpBlockExecutor gains three PostExecModes (Disabled / Produce /
  Verify(payload) / Invalid). Produce accumulates per-tx refund
  entries for the payload builder to append as a synthetic 0x7D tx;
  Verify validates an embedded payload against local replay;
  Disabled is the legacy path (byte-identical to pre-SDM).
- SDMWarmingInspector tracks first-warmer provenance for accounts
  and storage slots, emits exact refund attribution events for every
  re-touch past the EIP-2929 warm threshold, and suppresses claims
  from Deposit and synthetic PostExec tx kinds.
- Verify-mode validations reject duplicate payload indexes, payload
  entries targeting deposits or the 0x7D tx itself, and refunds that
  exceed the tx's raw gas. `apply_pre_execution_changes` debug_asserts
  the Produce hooks are wired so a downstream fork can't silently drop
  refunds.
- Canonical gas settlement credits the sender, debits the beneficiary
  and base-fee recipient by the refunded-gas component of their share,
  and commits the deltas when canonical gas falls below raw gas.
- beneficiary_gas_price can legitimately saturate at zero when a
  legacy tx's gas price equals basefee; inline comment documents the
  consensus-valid zero case.
@nonsense nonsense force-pushed the nonsense/sdm/post-exec-block-executor branch from 040f652 to 20bb8b6 Compare April 24, 2026 16:19
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.

SDM: Payload builder support for 0x7D post exec tx SDM: Execution-side block-level warming accounting SDM: hardfork gating

2 participants