feat(alloy-op-evm): post-exec block executor and SDM warming inspector#20213
Open
feat(alloy-op-evm): post-exec block executor and SDM warming inspector#20213
Conversation
d88b6a5 to
4fafc44
Compare
37f0705 to
052c1d6
Compare
einar-oplabs
approved these changes
Apr 23, 2026
| /// 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, |
Contributor
There was a problem hiding this comment.
Question to self: Is this enum shared between sequencer and verifiers?
| /// 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 { |
Contributor
There was a problem hiding this comment.
Question to sync review: How are all of these related?
| /// 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, |
Contributor
There was a problem hiding this comment.
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.
| /// 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()); |
Contributor
There was a problem hiding this comment.
Could probably take a ref.
| /// 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, |
Contributor
There was a problem hiding this comment.
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
| //! 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 |
Contributor
There was a problem hiding this comment.
Could we call the type parameter Transaction or TransactionType and make it default to OpTransactionSigned?
042cd79 to
513dbda
Compare
45bec29 to
040f652
Compare
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.
040f652 to
20bb8b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 versionedPostExecPayloadwith 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-enabledintegration-test override to exercise production, replay verification, and RPC receipt conversion paths.Related: #20216
Main changes
0x7Dand payload/validation helpers inop-alloy.alloy-op-evm-PostExecCompositeInspector<I>PostExecMode::Disabled,PostExecMode::Produce,PostExecMode::Verify.Review checklist
1. Activation / rollout safety
kona::RollupConfig::is_sdm_activereturnsfalse.op-reth::OpEvmConfig::is_sdm_active_at_timestampreturns onlysdm_enabled.OpPayloadBuilderCtx::sdm_production_enabledreturns onlybuilder_config.sdm_enabled.OpEvmConfig::new/optimism:sdm_enabled = false.OpBuilderConfig::new:sdm_enabled = false.OpReceiptConverter::new:sdm_enabled = false.--rollup.sdm-enabledis documented as integration-test/experimental only.0x7Dwhile SDM is disabled.2. New op-alloy consensus symbols
Review file:
rust/op-alloy/crates/consensus/src/post_exec.rsPOST_EXEC_PAYLOAD_VERSIONSDMGasEntry { index, gas_refund }indexsemantics are block transaction index.gas_refundmust be nonzero in verifier payloads.PostExecPayload { version, block_number, gas_refund_entries }block_numberanchors hash/replay to containing block. It there for the purpose of uniqueness.ParsedPostExecPayload { tx_index, payload }tx_indexis the index of the post-exec tx.PostExecPayloadValidationErrorparse_post_exec_payload_from_transactions(...)sdm_activeboolean from caller.TxPostExecbuild_post_exec_tx(...)Related transaction/receipt envelope changes:
OpTransaction::as_post_exechelper.OpTxEnvelope::PostExecvariant.OpTypedTransaction::PostExecvariant.OpTxType::PostExecvariant.OpReceiptEnvelope/ receipt handling for post-exec receipts.3. New alloy-op-evm post-exec symbols
Review files:
rust/alloy-op-evm/src/post_exec/mod.rsrust/alloy-op-evm/src/post_exec/inspector.rsrust/alloy-op-evm/src/lib.rsNew extension symbols:
PostExecTxKindNormalmay claim refunds.Depositwarms but cannot claim refunds.PostExeccannot claim refunds.PostExecTxContextPostExecExecutedTxPostExecEvmtraitPostExecEvmFactoryHooksPostExecEvmAdapterPostExecEvmFactoryAdapterPostExecExecutorExtInspector symbols:
SDMWarmingInspectorPostExecCompositeInspector<I>OpEvm:4. New alloy-op-evm block executor symbols / behavior
Review file:
rust/alloy-op-evm/src/block/mod.rsPostExecModeDisabledrejects0x7D.Producecollects entries and accepts builder-created post-exec tx.Verify(PostExecPayload)verifies embedded payload.PostExecStatePostExecAdjustmentOpBlockExecutionError::InvalidPostExecPayload(String)OpTxResultraw/canonical gas fields5. New op-reth EVM symbols
Review files:
rust/op-reth/crates/evm/src/lib.rsrust/op-reth/crates/evm/src/post_exec_ext.rsOpEvmConfig::sdm_enabledOpEvmConfig::with_sdm_enabled(...)OpEvmConfig::is_sdm_active_at_timestamp(...) -> boolconst fn.ConfigureEvm::context_for_blocksdm_active.EIP1559ParamError::InvalidPostExecPayload.ConfigureEngineEvm::context_for_payloadPostExecMode::Verifyonly when enabled and payload valid.ConfigurePostExecEvmtraitPostExecEvmFactoryAdapterimpl path6. op-reth payload builder symbols
Review files:
rust/op-reth/crates/payload/src/config.rsrust/op-reth/crates/payload/src/builder.rsrust/op-reth/crates/primitives/src/transaction/mod.rsOpBuilderConfig::sdm_enablednew_with_sdmonly explicitly enables it.OpPayloadBuilderCtx::sdm_production_enabled()build_post_exec_recovered_tx(...)try_include_post_exec_tx(...)Payload construction order:
BuildPostExecTransactiontrait7. op-reth node / RPC symbols
Review files:
rust/op-reth/crates/node/src/args.rsrust/op-reth/crates/node/src/node.rsrust/op-reth/crates/rpc/src/eth/mod.rsrust/op-reth/crates/rpc/src/eth/receipt.rsrust/op-reth/crates/rpc/src/error.rsNode config:
RollupArgs::sdm_enabled--rollup.sdm-enabled.OpExecutorBuilder::sdm_enabledandwith_sdm_enabled.OpPayloadBuilder::sdm_enabledandwith_sdm_enabled.OpAddOnsBuilder::sdm_enabledandwith_sdm_enabled.RollupArgsreaches all three:RPC:
OpEthApiBuilder::sdm_enabledandwith_sdm_enabled.OpReceiptConverter::sdm_enabledandwith_sdm_enabled.op_gas_refundis filled only from validated payload entries.OpEthApiError::InvalidPostExecPayloadmaps structural parser errors cleanly.8. Kona / proof changes
Review files:
rust/kona/crates/protocol/genesis/src/rollup.rsrust/kona/crates/proof/executor/src/builder/core.rsrust/kona/crates/proof/executor/src/errors.rsKona client/proof call-site generic-bound updates.
RollupConfig::is_sdm_active(...)always returnsfalse.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.rsrust/op-reth/examples/custom-node/src/evm/config.rsrust/op-reth/examples/custom-node/src/evm/executor.rsrust/op-reth/examples/custom-node/src/primitives/tx.rsrust/op-reth/crates/node/tests/it/builder.rsCustom EVM wrapper forwards
PostExecEvmhooks correctly.Custom EVM config exposes explicit post-exec constructors.
Custom executor implements/delegates
PostExecExecutorExt.Custom transaction enum implements
BuildPostExecTransaction.Uni custom precompile test uses
PostExecEvmFactoryAdaptercorrectly.Added generic bounds do not make custom-node extension ergonomics too difficult.
10. Test / invariant checklist
TxPostExecencoding/decoding/hash behavior.op_gas_refundfrom embedded payload with override enabled.High-risk areas to review extra carefully
0x7Dwithout the explicit override.TODO / questions to answer and review prior to merging:
developwith this PR.sdm_enabledis propagated correctly across the codebase.