feat(sdm): SDM warming-refund accounting, gated by flag#20173
feat(sdm): SDM warming-refund accounting, gated by flag#20173
Conversation
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
| "--chain=" + chainConfigPath, | ||
| "--proofs-history.storage-path=" + proofHistoryDir, | ||
| } | ||
| initOut, initErr := exec.Command(execPath, initProofsArgs...).CombinedOutput() |
There was a problem hiding this comment.
OS Command Injection (CWE-78)
More Details
OS command injection is a critical vulnerability that allows an attacker to execute arbitrary commands on the system. This can lead to a full system compromise, as the attacker can potentially gain complete control over the application and the underlying system.
The vulnerability arises when user input is used to construct commands or command arguments that are then executed by the application. This can occur when user-supplied data is passed directly to functions that execute OS commands, such as exec.Command, exec.CommandContext, syscall.ForkExec, or syscall.StartProcess.
If an attacker can inject malicious code into these commands, they can potentially execute any command on the system, including installing malware, stealing data, or causing a denial of service. This vulnerability can have severe consequences, including data breaches, system compromise, and unauthorized access to sensitive information.
To avoid this vulnerability, user input should never be used directly in constructing commands or command arguments. Instead, the application should use a hardcoded set of arguments and validate any user input to ensure it does not contain malicious code.
| Attribute | Value |
|---|---|
| Impact | |
| Likelihood |
Remediation
To remediate this vulnerability, follow these recommendations:
- Never use user-supplied input directly in constructing commands or command arguments.
- Validate and sanitize all user input before using it in any context.
- Use a hardcoded set of arguments for executing OS commands.
- If filenames are required, use a hash or unique identifier instead of the actual filename.
- Consider using a native library that implements the required functionality instead of executing OS commands.
Example of safely executing an OS command:
userData := []byte("user data")
// Create a temporary file in the application-specific directory
f, err := ioutil.TempFile("/var/app/restricted", "temp-*.dat")
if err != nil {
log.Fatal(err)
}
if _, err := f.Write(userData); err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
// Pass the full path to the binary and the name of the temporary file
out, err := exec.Command("/bin/cat", f.Name()).Output()
if err != nil {
log.Fatal(err)
}Rule ID: WS-I011-GO-00026
To ignore this finding as an exception, reply to this conversation with #wiz_ignore reason
If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more).
To get more details on how to remediate this issue using AI, reply to this conversation with #wiz remediate
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.
Extend op-reth's EvmConfig with the post-exec hooks the new OpBlockExecutor expects. Downstream uses: - The payload builder asks the executor to Produce + drains refund entries via post_exec_executor_for_block + take_post_exec_entries. - The replay RPC asks for the same Produce mode but on a stripped block (0x7D removed) to compare synthesized refunds against the embedded payload. OpEvm auto-wires the SDMWarmingInspector begin/take hooks so callers don't have to plumb them manually; the alloy-op-evm debug_assert guards the failure mode if a downstream fork bypasses OpEvm.
Append a type-0x7D post-exec transaction at the tail of the block when the sequencer builds under --rollup.sdm-enabled. The tx carries the executor's accumulated refund entries as its RLP payload and canonicalizes this node's gas accounting with what a verifier will later independently replay. - OpBuilderConfig/CLI flag (`--rollup.sdm-enabled`) — off by default. When off, the payload path is byte-identical to the pre-feature code. - try_include_post_exec_tx wraps the executor's refund entries in a TxPostExec, executes it, and aborts the payload build with PayloadBuilderError::EvmExecutionError on any synthetic-tx execution failure. Silently dropping it would yield a payload that no honest verifier can reproduce. - Unit tests pin the abort path (should-not-be-Ok-on-failure), the no-entries skip, and the happy-path wrapping of entries. - custom-node example switches to NoopPayloadServiceBuilder; the upstream OpPayloadBuilder is now specialized for OpTransactionSigned to carry the post-exec tx and no longer composes with the example's custom tx type. Doc comment explains what downstream forks need.
Introduce reth-optimism-post-exec-replay, a new crate that counterfactually re-executes a historical block under PostExecMode::Produce to derive what the post-exec payload *would* have been, then compares it to any 0x7D tx already embedded in the block and to the receipt-level opGasRefund projection. - debug_replaySDMBlock JSON-RPC handler exposes the replay result on a per-block basis. Compares synthesized refunds vs. embedded payload and vs. receipt refunds when requested. Documented as operator/debug tooling only — the debug namespace must not be exposed on public RPC, each call replays a whole historical block against live state and is unbounded in cost. - Replay detects seven mismatch categories: duplicate payload index, payload-index-out-of-range, payload-targets-deposit, payload-targets-post-exec, payload-refund-mismatch, receipt-refund-mismatch, payload-refund-exceeds-raw-gas. - strip_post_exec_tx_for_replay preserves the original tx index mapping so refund events can be attributed back to source-block positions. - Semaphore::new(3) caps concurrent replays; per-call wall-clock and memory ceilings are a production-hardening gap tracked separately.
Client for driving debug_replaySDMBlock over a range of blocks and writing a JSONL trail of the replay results, for post-hoc verification and cross-node consistency checks. - ReplayRange iterates blocks with ctx cancellation support so long ranges can be aborted cleanly. - DecodePayload RLP-decodes the 0x7D tx input and rejects unknown versions in lock-step with the Rust decoder (POST_EXEC_PAYLOAD_VERSION = 1). Cross-language drift would let a Go pipeline accept payloads the Rust node rejects. - Unit tests cover the version check on both the current 3-field shape and the legacy 2-field fallback, plus an empty-input guard. - Memory-buffered range mode is fine for devstack tests; streaming JSONL for mainnet-scale ranges is tracked as a pre-production item.
Devstack-backed acceptance tests that boot an op-reth sequencer with --rollup.sdm-enabled, submit a repeated-slot workload, and assert the full SDM pipeline: sequencer-produced 0x7D tx is present with the expected refund entries, receipt-level opGasRefund matches the payload, and debug_replaySDMBlock reproduces the same refunds from state replay. Also covers SDM-disabled baseline, dedup + refund-cap behavior across same-slot and many-slot workloads, and a multi-category smoke test. Mixed-runtime devstack plumbing in op-devstack/sysgo wires the SDMEnabled flag from node spec to op-reth args.
Producer-side and follower-side metrics so operators can monitor a
canary before flipping SDM on via hardfork.
- optimism_sdm.payload_builder (in reth-optimism-payload-builder):
* blocks_with_post_exec_tx / blocks_without_post_exec_tx counters
split on whether the sequencer appended a synthetic 0x7D tx,
* block_refund_gas / block_refund_entry_count histograms over the
refund payload size, per block with a 0x7D.
- optimism_sdm.replay (in reth-optimism-post-exec-replay):
* blocks_total / blocks_with_mismatch_total counters recorded on
every replay_block() call,
* per-category mismatch counters for each
PostExecReplayMismatchKind, so an operator running
`sdmreplay --fail-on-mismatch` can alert on both which block
diverged and which rule fired.
Default-instantiated at the call site — handles are cheap and
registry-backed. Verifier-mode metrics in alloy-op-evm are deferred
because that crate is no_std-capable and pulling in reth-metrics
would break the feature gating.
c251c31 to
b51b9bd
Compare
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## develop ethereum-optimism/optimism#20173 +/- ##
==========================================
- Coverage 76.0% 66.3% -9.7%
==========================================
Files 183 55 -128
Lines 10536 4035 -6501
==========================================
- Hits 8012 2677 -5335
+ Misses 2380 1214 -1166
Partials 144 144
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
This PR is introducing SDM - Sequencer-Defined Metering.
Design doc: https://github.com/ethereum-optimism/design-docs/blob/main/protocol/sdm.md
Tracking issue: https://github.com/ethereum-optimism/protocol-team/issues/59
PR is heavy WIP, and ideally will be refactored further so that it is easier for review.
Follow-up from #19954
Per-commit summaries
feat(op-alloy): add post-exec (0x7D) transaction type
Hardened post-exec transaction already included in previous PR.
feat(alloy-op-evm): post-exec block executor and SDM warming inspector
Core consensus change.
SDMWarmingInspectortracks first-warmer provenanceand emits refunds.
feat(op-reth): add ConfigurePostExecEvm and post-exec-aware evm config
Plumbing for
op-rethfeat(op-reth): inject synthetic post-exec tx in payload builder
Appends the 0x7D tx at block tail when
--rollup.sdm-enabled.feat(op-reth): add debug_replaySDMBlock rpc and post-exec-replay crate
New
reth-optimism-post-exec-replaycrate re-executes a historical block underProduceand compares the synthesized payload to the embedded0x7Dand the receipt-level refund projection.feat(op-chain-ops): add Go sdmreplay client
Go driver for
debug_replaySDMBlocktest(op-acceptance-tests): add end-to-end SDM coverage
Devstack tests that boot an
op-rethsequencer with SDM enabled.feat(op-reth): add SDM observability metrics
SDM observability
TODO: