Skip to content

feat: merge v2.3.3 - #9

Merged
0byteme merged 167 commits into
mainfrom
merge-v2.3.3
Jul 1, 2026
Merged

0byteme merged 167 commits into
mainfrom
merge-v2.3.3

Conversation

@0byteme

@0byteme 0byteme commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

ajsutton and others added 30 commits June 5, 2026 07:54
prepare-continuation-config set BASE_REVISION: devlop. collect-params.sh
computes the changed-file set with git diff --name-only "origin/${BASE_REVISION}...HEAD"
and, on failure, silently falls back to git diff HEAD~1 HEAD. Because
origin/devlop does not exist, the diff against the merge base never ran and
change detection only saw the *last commit* of a multi-commit PR — so a PR
that changed rust/ or contracts in an earlier commit but ended on a docs-only
commit could be misclassified and skip its required tests.

Reproduced: origin/devlop does not resolve (triggering the HEAD~1 fallback);
origin/develop resolves. No unit test added: collect-params.sh is not
unit-tested and test-decision-tree.sh stubs the parameter JSON rather than
exercising the diff, so there is no natural harness for this one-line config
value; the fix is verified by ref resolution above.
The op-e2e/config init() unconditionally ran a full op-deployer genesis
pipeline for all 4 alloc types across 9 hardfork modes (36 pipelines) at
test-binary startup, costing minutes of CPU before any test ran. The
op-e2e-fuzz job runs three concurrent `go test -fuzz ./opgeth` processes;
each paid this full cost, oversubscribing CPU on the CI box. Under load the
combined startup exceeded the 15m no-output timeout, killing the job with
zero output from all three targets (including the pure-Go FuzzFastLzCgo,
which shares the binary and was blocked behind the same init).

Generate each alloc type lazily and once, so a binary only pays for the
alloc types its tests request. The opgeth fuzz binary uses only
DefaultAllocType, cutting its work from 4 alloc types to 1, and the two
FastLz targets need none, so they now start and stream output in seconds.

Refs ethereum-optimism/optimism#21191
…andoff bootstrap (#21220)

* test(devstack): un-skip #21164 light-sequencer interop tests via VN-handoff bootstrap

A follow-mode ELSync light sequencer cannot bootstrap a chain from genesis: as
the sole block producer it has no peer payload to initial-EL-sync from, so it
deadlocks in willStartEL (#21164). This left two interop tests skipped. Bootstrap
them instead by sequencing on the supernode VN first, then handing off to the
light sequencers, and un-skip:
- TestLightSequencerSupernodeDerivesSafeChain
- TestSupernodeLightSequencerInteropInvalidMessageReplacement (op-reth only)

Preset / runtime
- WithSupernodeVNSequencerForBootstrap: the supernode VN sequences and the light
  follow-mode ELSync sequencers start stopped (l2CLNodeStartConfig.SequencerStopped
  -> driver.Config.SequencerStopped). The VN produces + gossips so the light seqs
  leave willStartEL via initial EL-sync.
- BootstrapLightSequencersViaVNHandoff helper: peers light<->VN, waits for the
  light seqs to EL-sync out of willStartEL, then stops sequencing on the VN and
  starts it on the light CLs.
- Batcher follows the VN during bootstrap, not the (stopped, EL-syncing) light
  seqs. Batching the light seqs leaves a gap at the handoff boundary that wedges
  L1 derivation ("dropping future span batch" forever) so the safe head never
  advances and interop verification stalls. The VN produces during bootstrap and
  tracks the light seqs via gossip after handoff, keeping a gap-free batch stream.
- Expose the supernode VN's EL per chain as L2ASupernodeEL / L2BSupernodeEL (a
  distinct node from L2ELA/L2ELB in light-sequencer mode; aliases them in
  virtual-sequencer mode).

invalid-message scenario
- Settle on cross-safe BEFORE asserting the invalid tx is gone. The unsafe head
  oscillates during recovery (#21119) and can reorg back onto an invalid-containing
  fork after the replacement validates; reading the block by number off the unsafe
  tip caught that transient. Once cross-safe advances past the block it is
  irreversible, so the read is stable.
- Anchor the post-recovery new-tx check on the supernode VN's L1-derived safe
  chain (the canonical source of truth): follow the tx by hash until it lands at or
  below the supernode's safe head, then assert it is present on BOTH the light
  sequencer's EL and the supernode VN's EL. This proves producer and verifier
  converge on one canonical chain, instead of trusting the producer's oscillating
  tip via a block-number comparison.
- MarkFlaky(#21125) for the residual reorg-recovery flake (a tx sent mid-storm can
  be orphaned even though the chain recovers). MarkFlaky only downgrades failures
  on the testingT it is called on, and DSL node assertions route through each
  node's own testingT, so it is applied to every node exercised in recovery, not
  just the test-body t.

* test(devstack): extract ResendUntilSafe DSL helper

Move the resend-with-inclusion-check loop out of the invalid-message
replacement test and into a reusable dsl.L2ELNode.ResendUntilSafe helper
that takes a tx-generator and waits for a broadcast to derive onto the
node's safe chain. Uses the tolerant blockRefByLabel getter inside the
retry so a transient safe-head lookup error retries instead of unwinding
the goroutine.

* test(devstack): make VN-handoff bootstrap a system method

Move BootstrapLightSequencersViaVNHandoff from a standalone presets
function to a method on *TwoL2SupernodeInterop, alongside the other
system methods in twol2.go, so tests read as
sys.BootstrapLightSequencersViaVNHandoff(). Update both call sites and
the WithSupernodeVNSequencerForBootstrap doc reference.

---------

Co-authored-by: wwared <[email protected]>
…#21227)

* ci(rust): branch-scoped sccache GCS compile cache for Rust builds

Back the Rust CI compile cache with the op-rust-sccache GCS bucket, with
write access restricted to the develop branch.

- use sccache preinstalled in ci-base-clang (bump the effective rust_base_image
  pin in config.yml + the continuation defaults)
- GCP Workload Identity coords, reader service account, and bucket come from
  the project-restricted sccache-gcs-optimism CircleCI context attached to the
  rust-ci jobs
- read-only reader identity is the default for every ref; develop overrides to
  the read/write writer identity via a native when/equal on pipeline.git.branch
- the write/read split is enforced by GCP Workload Identity on the signed
  CircleCI OIDC vcs-ref claim, so a non-develop ref cannot gain write by editing
  this config; the condition only selects the service account
- sccache error log + idle timeout for observability/robustness
- prints sccache --show-stats at job end

* ci(rust): make sccache setup fail-open and drop the gcloud dependency

The sccache setup ran gcp-cli/install + gcp-oidc-authenticate as unguarded
steps, so a gcloud install break or a job missing the sccache-gcs-optimism
context hard-failed the whole rust job. sccache is an accelerator, not a gate.

- build the Workload Identity ADC config directly (no gcloud) and let sccache
  consume it via GOOGLE_APPLICATION_CREDENTIALS
- skip with a warning (never fail) when the context/coords are absent, sccache
  is missing, or the server won't start

* ci(rust): attach sccache-gcs-optimism to all Rust workspace builds

The context was only on the rust-ci anchor jobs; the heavy workspace
compiles in the main + rust-e2e workflows (rust-workspace-binaries,
rust-workspace-release, op-rbuilder, rollup-boost) ran without it and so
fell through to the cache-less fail-open path. Attach the context to those
invocations too so every Rust build on ci-base-clang uses the shared cache.

* ci(rust): use sccache in the vendored builds (op-rbuilder, rollup-boost)

Review caught that rust-build-vendored ran a bare cargo build without
RUSTC_WRAPPER, so op-rbuilder and rollup-boost carried the sccache context
but never used the cache. Invoke rust-sccache-setup in the job and set
CARGO_INCREMENTAL=0 in the command (this job does not run rust-setup-env),
so a vendored rebuild on a binary-cache miss is sccache-accelerated.

* ci(rust): slim down the sccache start step

Drop the redundant guards (sccache is baked into ci-base-clang; bad/absent
coords just make --start-server fail). Keep the one invariant that matters:
set RUSTC_WRAPPER only when the server actually starts, so a cache/auth/GCS
hiccup yields an uncached build rather than failing every rustc call.

* ci(rust): fail loud if sccache cannot start; cover all routing jobs

Drop the fail-open fallback: set RUSTC_WRAPPER and let the job fail if
sccache --start-server fails, so a broken cache surfaces immediately instead
of silently building uncached. Because rust-sccache-setup now fails loud,
every job that routes through it must carry the sccache-gcs-optimism context
— add it to the kona-publish-prestate-artifacts invocations (the only routing
jobs that lacked it).

* ci(rust): skip sccache where it is not installed (machine-executor jobs)

The cannon jobs (kona-build-fpvm/host-offline/lint) run on a VM machine
executor, not the ci-base-clang image, so sccache is absent and the fail-loud
start hit 'command not found'. Skip when the binary is missing; still fail
loud when sccache is installed but the server won't start.

* ci(rust): dedupe rust-prepare/rust-setup-env into helpers.yml; drop vendored dup

Address review: rust-prepare and rust-setup-env were defined in both main.yml
and rust-ci.yml, with the sccache wiring living only in the rust-ci.yml copy
that happens to win the deep-merge — a footgun (consolidating toward the
main.yml copy would silently disable sccache). Move the single canonical copy
into helpers.yml (merged first), so there is exactly one of each and the
sccache step can't be shadowed. Also remove the byte-identical duplicate
rust-build-vendored definition.

* ci(rust): dedupe Rust cache command definitions

rust-ci.yml redefined rust-build, rust-prepare-and-restore-cache,
rust-restore-build-cache and rust-save-build-cache, duplicating the
canonical definitions in main.yml. Because merge-configs.sh merges
rust-ci.yml after main.yml (later wins), the rust-ci.yml copies silently
shadowed main.yml's, dropping two behaviours that only existed in the
main.yml version:

  - the rust_files_changed fast path that skips cargo build on feature
    branches when rust/ is unchanged (parameter was still passed but
    ignored), and
  - the --bin <binary> filter (BINARY was set but never appended to the
    cargo invocation).

Remove the duplicates so the main.yml definitions are the single source
of truth. No behaviour change beyond restoring the two dropped paths;
merged config validates with circleci config validate.

* ci(rust): share the Rust dependency cache across all jobs

The crate-download cache (.cargo/registry, .cargo/git, .cargo/bin,
.rustup) was keyed on the per-job prefix, so each of the ~18 Rust CI
jobs maintained its own copy even though the downloaded crate set is
identical for a given Cargo.lock — independent of job, profile, and
features. On any cold cache every job downloaded the full dependency
set from crates.io in parallel and CircleCI stored a separate multi-GB
copy per job.

Key this cache on a single shared 'rust-deps' prefix (still per-workspace
via the Cargo.lock checksum) so the dependencies are fetched and stored
once and reused by every job. The per-job target/ cache is unchanged.

No fallback key is used on purpose: a Cargo.lock change fully refreshes
the dependency cache rather than accreting old crate versions on top of
the existing set, bounding cache size. Lock changes are infrequent.

* ci(rust): cache the Rust toolchain separately from dependencies

.rustup and the cargo-installed binaries (.cargo/bin) were stored in the
dependency cache keyed on Cargo.lock, so every dependency bump threw away
and re-downloaded the entire Rust toolchain — even though the toolchain
is pinned by mise.toml and rust/rust-toolchain.toml, not Cargo.lock.

Split them into a dedicated 'rust-toolchain' cache keyed on those pin
files. The toolchain is now refreshed only when the pins change, while
the dependency cache holds just the downloaded crate sources (registry +
git). Both remain shared across all jobs.

* ci(rust): re-attach sccache stats to the consolidated rust-save-build-cache

The sccache --show-stats step lived in the rust-ci.yml copy of
rust-save-build-cache, which was removed by the dedup. Re-add it to the
single canonical rust-save-build-cache in main.yml.

---------

Co-authored-by: Adrian Sutton <[email protected]>
The image-build smoke check runs '<image> <image_name> --version', which
exercises only the image's primary binary. op-challenger is the one fat
image that bundles helper binaries (op-program, cannon, kona-host) and
execs them at runtime, so a broken bundled binary is invisible to that
check.

This gap let a runtime-loader mismatch ship: cannon is cgo/musl-linked
and could not exec on a glibc-only base image, while 'op-challenger
--version' kept passing.

Add a step that execs each bundled binary on both arch runners and fails
on exit 126/127 (not executable / missing loader) or a signal-termination
exit (>=128, e.g. 139 SIGSEGV).
…0581)

* feat(op-node): serve superroot_atTimestamp for non-interop chains

Add a new `superroot` JSON-RPC namespace on op-node with method
`AtTimestamp(timestamp)`, mirroring op-supernode's wire contract
exactly so existing clients (op-challenger, op-dispute-mon) can
point `--supernode-rpc` at op-node URL on non-interop chains.

The handler reuses op-node primitives only — driver SyncStatus +
BlockRefWithStatus, l2EthClient OutputV0AtBlock, and the shared
safedb.L1AtSafeHead walkback — to construct a single-chain
SuperRootAtTimestampResponse byte-compatible with op-supernode's
output for the same chain.

Field semantics are documented in code:

  - CurrentSafeTimestamp / CurrentLocalSafeTimestamp / CurrentFinalizedTimestamp
    pulled directly from SyncStatus, matching op-supernode's per-field
    aggregation collapsed to one chain.
  - VerifiedRequiredL1 (and OptimisticAtTimestamp[chainID].RequiredL1
    on the verified branch) come from safedb.L1AtSafeHead — this is
    the value op-challenger compares against l1Head to gate
    InvalidTransition, so it is correctness-critical, not perf.
  - Optimistic-only branch (block beyond LocalSafeL2 but within UnsafeL2)
    uses the L2 block's L1Origin as RequiredL1, mirroring op-supernode.
  - Pre-genesis / beyond-UnsafeL2 / safeDB-not-found all degrade to
    populated sync fields with Data nil — never zero-out the response
    on transport errors (consumers check CurrentL1.Number first).

Per the issue's resolution by @ajsutton, no activation gate: the API
behaves the same pre- and post-Interop, since a single-chain super
root is well-defined at any timestamp.

Unit tests cover: pre-genesis, beyond-unsafe, optimistic-only,
verified happy path, safeDB-not-found fallback, driver/output
errors, and a runtime SuperRootProvider conformance check (mirror
of op-challenger's and op-dispute-mon's consumer interface).

The compatibility check uses a test-only adapter rather than adding
a SuperRootAtTimestamp method to the live struct — go-ethereum
auto-registers exported methods on RPC services, so a method on the
live struct would leak `superroot_superRootAtTimestamp` as a wire
method. The test-only adapter keeps the wire surface to exactly
`superroot_atTimestamp`.

Refs: ethereum-optimism/optimism#20552

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fixup(op-node): match op-supernode fallback semantics exactly

Address security review on PR #20581. Three fallback branches now
mirror op-supernode chain_container behavior precisely so the new
op-node handler is byte-for-byte equivalent for the single-chain
case across all paths op-challenger and op-dispute-mon exercise:

1. blockNum > LocalSafeL2.Number — was returning an optimistic
   entry with RequiredL1 = ref.L1Origin. op-supernode's
   LocalSafeBlockAtTimestamp returns ethereum.NotFound here, so
   both VerifiedAt and OptimisticAt fail and the chain is OMITTED
   from OptimisticAtTimestamp. Now omits.

2. safeDB returns ErrL1AtSafeHeadNotFound (transient) — was
   returning an optimistic entry with L1Origin. op-supernode's
   safeDBAtL2 maps this to ethereum.NotFound, OptimisticAt fails,
   chain is OMITTED. Now omits.

3. safeDB returns ErrL1AtSafeHeadUnavailable (permanent gap) — was
   silently degrading to optimistic-only. op-supernode's safeDBAtL2
   maps this to ErrHistoryUnavailable which bubbles up as an RPC
   error (operator must intervene). Now returns the wrapped error.

The previous behavior could cause op-challenger's
SuperNodeTraceProvider at step > 0 to include a chain in
expectedState.PendingProgress that op-supernode would have
rejected via InvalidTransition (the consumer reads
OptimisticAtTimestamp[chainID] without checking Data == nil at
non-zero steps).

Tests rewritten to assert the new semantics:
  - TestSuperrootAPI_BeyondLocalSafe_OmitsChain
  - TestSuperrootAPI_SafeDBNotFound_OmitsChain
  - TestSuperrootAPI_SafeDBUnavailable_ReturnsError

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fixup(op-node): reject superroot_atTimestamp when SafeDB disabled

Optional-interface assertion at the top of superrootAPI.atTimestamp:
if the configured SafeDBReader is *safedb.DisabledDB (Enabled()==false),
return a clear error before doing any work. Without this gate, requests
within LocalSafeL2 surface ErrNotEnabled from the helper while requests
beyond LocalSafeL2 short-circuit successfully with Data=nil — an
operator who forgot --safedb.path on a non-interop chain serving
dispute infra would see inconsistent responses and op-challenger would
loop on empty data.

op-supernode does not need an equivalent check: chain_container hard-
codes vncfg.SafeDBPath at virtual-node construction, so SafeDB is
always enabled there. On op-node --safedb.path is optional, so this is
a new failure mode unique to the non-interop path.

Test uses the real safedb.Disabled singleton and sets no driver
expectations — if the handler ever calls SyncStatus before rejecting,
the testify mock fails the test, asserting both the error and the
"before any work" semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fixup(op-node): address review on superroot_atTimestamp

Address ajsutton review on PR #20581:

- Pre-genesis now returns an error from rollup.TargetBlockNumber instead
  of silently succeeding with Data nil. Dispute infra never legitimately
  queries before genesis; a clear error beats an ambiguous empty success.
- Drop the explicit safedb.Enabled() gate. safedb.Disabled.L1AtSafeHead
  returns ErrNotEnabled, which now propagates as a real error rather
  than being treated like transient SafeDB lag.
- Restructure to call BlockRefWithStatus at the top instead of a separate
  SyncStatus + BlockRefWithStatus pair, so the LocalSafeL2 bound check is
  consistent with the resolved ref (no race). A SyncStatus fallback runs
  only when BlockRefWithStatus fails (block beyond unsafe head).
- Only ErrL1AtSafeHeadNotFound (transient) maps to Data nil. All other
  errors (ErrL1AtSafeHeadUnavailable, ErrNotEnabled, network/IO) now
  propagate to the RPC caller.
- Drop the superrootAPIQueryAdapter shim and the SuperRootProviderCompat
  test. Tests call atTimestamp directly; wire compatibility is the real
  contract with op-challenger / op-dispute-mon.
- Trim verbose comments throughout.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fixup(op-node): classify BlockRef/Output errors by ethereum.NotFound

Match op-supernode parity: only ethereum.NotFound from BlockRefWithStatus
or OutputV0AtBlock omits the chain. All other errors (context deadlines,
EL transport failures, driver-loop errors) fail the RPC rather than
silently degrading to an empty response.

Also drop the trailing blank line at EOF in the test file.

* fixup(op-node): trim comments in superroot files

* test(op-node): cover the L2-genesis superroot special case

* fixup(op-node): tighten superroot_atTimestamp error/omit policy

* fixup(op-node): drop unused logger from superrootAPI

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
* feat(op-reth/proof): backfill initial implementation

* perform backfill against proof store

* improvements

* progress logging

* doc and test fixes

* fmt fixes and removed blanket impl

* improvements and fixes

* added test

* feat(op-reth/proofs): one-time trie-state snapshot

* removed unnecessary changes

* fix rustdoc

* bugfixes

* snapshot earliest -> anchor

* split providers

* doc updated

* feat(op-reth/proofs): integrate snapshot into backfill

* minor improvement

* feat(op-reth/proofs): snapshot hashed-leaf tables for backfill

* backfill not implemented for immemory and v1 store

* clippy fixes

* refactor trait

* metric removed

* move snapshot related CompletionEstimatable

* move snapshot related CompletionEstimatable

* review fixes
Move the EIP-4844 RootsOfUnity table out of op-program/client/l1 into a new
op-service/kzg package, so op-challenger no longer imports op-program.

op-service (rather than op-challenger) is the right home: op-program/client is
the fault-proof program compiled for cannon, and its build context (see
op-program/Dockerfile.vmcompat.dockerignore / Dockerfile.repro.dockerignore)
allowlists op-service but not op-challenger. Since client/l1 itself uses
RootsOfUnity, it must stay importable from the FPVM client; op-service is the
neutral package both op-challenger and op-program may depend on.

Prerequisite for deleting op-program from the monorepo (ethereum-optimism/optimism#20276).
…254)

* chore(op-devstack): remove Cannon game type (op-program) support

Remove the legacy Cannon game type (game type 0, served by op-program) from
op-devstack as part of removing op-program from the monorepo
(ethereum-optimism/optimism#20276).

PermissionedCannon (game type 1) is kept but configured with a dummy absolute
prestate (0x..dead) so the challenger never executes the legacy fault-proof
program: permissioned games skip prestate validation and resolve without
reaching step(). The Cannon VM oracle server is pointed at the cannon binary
(unused) instead of op-program.

op-devstack no longer references op-program/bin/*. The op-program Cannon wiring
that op-e2e still depends on is moved into op-e2e itself.

* review: op-e2e permissioned games use dummy prestate; drop dead Cannon helpers

- Switch op-e2e's base Cannon VM config to the shared WithPermissionedCannonConfig
  (dummy 0x..dead prestate), so op-e2e's challenger no longer references the
  op-program prestate. Permissioned games resolve at the output-root level without
  reaching step(), so the dummy is sufficient; verified via TestPermissionedGameType
  and TestOutputCannonGame.
- Delete dead WithCannon (op-e2e) and WithCannonGameType (op-devstack): op-e2e
  exercises only cannon-kona and permissioned games, never the legacy Cannon type.
- Drop the now-broken op-program prestate os.Stat checks from op-e2e's challenger
  config validation (mirrors op-devstack).
- Unexport FindMonorepoRoot; op-e2e uses op-service.FindMonorepoRoot.
- Rename proofs/cannon_kona package from 'cannon' to 'cannon_kona' to match the dir.

* review: unexport applyCannonVMConfig

op-e2e now uses WithPermissionedCannonConfig, so the base VM-config helper is only
used internally by op-devstack/shared/challenger. Make it unexported.

* review: remove orphaned PrestateVariant

Switching op-e2e to the dummy permissioned prestate removed the last caller of
.PrestateVariant(). Remove the now-dead chain end to end:
- PrestateVariant type + MTCannon/MTCannonNext/Interop/InteropNext constants
  (op-devstack/shared/challenger)
- PrestateVariant() from the System interfaces (challenger + disputegame helpers)
  and its implementations (e2esys.System, SuperDisputeSystem)
- now-unused imports (shared/challenger, op-e2e/config).

* review: use DummyPermissionedPrestate constant and clarify VM config name
… (#21245)

apt-install, install-zstd, and gcp-oidc-authenticate were each defined in
both main.yml and rust-ci.yml. Under the continuation merge (helpers -> main
-> rust-ci -> rust-e2e, later file wins) main.yml holds each command's
position in the merged output while rust-ci.yml's body wins on value.

Remove the rust-ci.yml duplicates and keep the canonical copies in main.yml.
Align main.yml's gcp-oidc-authenticate body to the (comment-less) version
that previously won, so the merged value is unchanged. Verified byte-identical:
re-running merge-configs.sh and diffing the merged output against develop
shows no difference.
- Bump op-reth to v2.3.1 in the Upgrade 19 (Karst) component table
- Regenerate release pages: op-reth now leads with v2.3.1; buggy
  v2.2.5 and v2.3.0 (superseded by v2.3.1) dropped; picks up newer
  op-contracts, op-deployer and other component releases
- Move op-reth and op-contracts under the Releases "Protocol" nav
  group and remove the "Execution" and "Contracts" sub-groups
  (docs.json + generate-releases.ts group metadata)

Co-authored-by: soyboy vegan <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
…2.4.3 (#21265)

Removes the stale `@custom:audit none` comment now that SuperchainConfig
has been audited. Patch version bump per semver policy (comment-only change).

Co-authored-by: Claude Sonnet 4.6 (1M context) <[email protected]>
* op-devstack: sequence on op-reth for Karst+ in acceptance tests

op-geth is deprecated as of the Karst fork and (with op-geth#802) refuses to
build, seal, or import Karst blocks. Make the acceptance-test harness use
op-reth — the official Karst EL client — for Karst-activated chains.

- Route the two presets that hardcoded the op-geth EL through the env-aware
  dispatch (startSequencerEL / startL2ELForKey), so they honor
  DEVSTACK_L2EL_KIND instead of always running op-geth: the sync-tester
  sequencer and the follow-L2 followers.
- Add a Karst-aware guard at the EL dispatch point: when a chain activates
  Karst at genesis, skip env-selected op-geth (the op-geth CI lane cannot
  cover Karst) and fail loudly when a mixed-runtime node spec explicitly pins
  op-geth (a test misconfiguration). Everything else sequences/verifies on
  op-reth. No change for <= Jovian chains.

Closes #21182.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* op-devstack: centralize Karst op-geth guard in buildL2Genesis

Address review feedback (joshklop): the per-dispatch guard missed op-geth EL
start paths that bypass startL2ELForKey (e.g. startSupernodeELWithInteropURL,
which constructs *OpGeth directly). Move the env-driven skip to buildL2Genesis
— the single point every runtime builds genesis through — so it covers all
runtimes and EL-start paths.

- buildL2Genesis: skip when an L2 chain configures Karst (KarstTime != nil)
  and DEVSTACK_L2EL_KIND=op-geth. Using KarstTime != nil (not just
  IsKarst(genesis)) also covers chains that activate Karst at an offset, which
  op-geth cannot cross either.
- Drop the now-redundant startL2ELForKey guard.
- Keep the mixed-runtime per-node fail for an explicit op-geth spec on a Karst
  chain (a misconfiguration not covered by the env-driven genesis guard).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* op-devstack: drop redundant mixed-runtime op-geth Karst guard

Address review (joshklop): no preset sets a per-node op-geth EL spec, and the
env-driven op-geth lane is already covered by the buildL2Genesis guard, so the
per-node guard was unreachable. Remove it; the switch's default case still
rejects unsupported EL kinds.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
…s (#20937)

The --proofs-history.prune-interval flag was removed in op-reth v2.2.3
(commit 97356ff, PR #20450) when pruning moved into the engine task
and now runs inline with each persistence batch. Drop the now-broken
flag from the exex README and public docs, and rewrite the surrounding
narrative so operators do not chase a knob that no longer exists.

Co-authored-by: soyboy vegan <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Co-authored-by: geoknee <[email protected]>
…debug_dbGet (#21269)

debug_executePayload (the L2PayloadWitness hint) and the L2BlockData
optimistic-block re-execution are "high-level" hints that bulk-populate the
preimage store. They were fetched once when received and discarded, so any
error was terminal: the client fell back to per-node debug_dbGet, which
op-reth does not implement, turning a transient failure into a permanent one.

Instead of fetching high-level hints eagerly, retain the latest one and let
the existing get_preimage retry loop service it: each attempt tries the
high-level hint first (bulk-populating the store), then the fine-grained
hints. A high-level hint is cleared once it succeeds (its response is
deterministic) and kept while it fails, so a transient error is retried
indefinitely without re-running an already-satisfied hint. debug_dbGet
remains the fallback only when the node genuinely lacks debug_executePayload
(JSON-RPC method-not-found).

is_rpc_method_not_found and the debug_executePayload call are consolidated
into backend::util, removing the duplication between the single-chain and
interop handlers.
* fix(op-challenger): tear down VM process group on timeout

The VM (cannon) spawns the oracle/preimage server as its own child, which
inherits the VM's stdout/stderr pipes. exec.CommandContext only SIGKILLs the
direct child on timeout, so the oracle server is orphaned and keeps the pipe
write ends open. cmd.Wait then blocks forever and the VM timeout never actually
terminates a wedged run.

Run the VM in its own process group, kill the whole group on cancellation, and
set a bounded WaitDelay as a backstop so RunCmd always returns once the context
is cancelled.

* test(op-challenger): cancel only after process tree spawns

Arm the teardown after the grandchild records its pid rather than on a
fixed 200ms timer, so the test exercises the deadlock instead of racing
cancellation against process startup on an overloaded CI box.
…) (#21274)

proc-macro-error2 is flagged unmaintained by RUSTSEC-2026-0173 (no
vulnerability, no patched release exists). It is a build-time-only proc-macro
dependency pulled in via alloy-sol-macro; the latest alloy-core (1.6.0) and its
main branch still pin it because they use a private API, so no upgrade removes
it. Add a scoped advisory ignore matching the existing unmaintained/transitive
ignores in deny.toml.
Decouple op-e2e from op-program so it can be deleted from the monorepo
(ethereum-optimism/optimism#20276). After this, neither op-e2e nor
op-devstack reference op-program.

- Move the engineapi package (the in-process L2 engine used by action
  tests) from op-program/client/l2/engineapi into
  op-e2e/actions/helpers/engineapi.
- Stop deploying the legacy type-0 Cannon dispute game in the op-e2e
  deploy intent. It is no longer served by op-challenger, and it was the
  only reason op-e2e read op-program/bin/prestate-proof-mt64.json. The
  generic CannonKona (type 8) game still exercises the proof system.
…ned game (#21270)

* chore(op-challenger): don't require --cannon-server for permissioned game

The permissioned game type is played only by trusted actors and resolves at
the output-root level without ever reaching step(), so it never runs
op-program. Make the --cannon-server flag (and the runtime op-program server
binary existence check) optional when only the permissioned game type is
enabled. The legacy Cannon game type (0) still requires it, as does CannonKona
(kona-host). Part of removing op-program from the monorepo
(ethereum-optimism/optimism#20276).

* test(op-challenger): cover cannon+permissioned both-enabled server requirement

When both the cannon and permissioned game types are enabled, --cannon-server
must still be required because the cannon game runs op-program. Add config and
CLI tests guarding this both-enabled path, which was previously uncovered.
* docs(upgrade-19): draft kona-client prestate verification step

Draft for review. Uses kona-client/v1.5.2 hashes + Tabs for standard
vs custom-config chains; kona-client version and hashes will change
when the final U19 release tags are cut. Custom-config build flow
linked to #21000.

Closes ethereum-optimism/solutions#681

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* docs(upgrade-19): use kona-client v1.6.0 prestate hashes

Update the kona-client prestate verification step for superchain-registry
chains to the v1.6.0 release tag and its absolute prestate hashes:
cannon64-kona and cannon64-kona-interop.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(upgrade-19): add embedded-config check + fix custom prestate on-chain verify

- Add a pre-deployment embedded-config check (gunzip | strings | grep <chain>)
  for custom prestates, run once per chain — catches a silently-dropped chain
  that otherwise yields the standard hash.
- Fix the on-chain verification: on v2.4+ contracts the prestate lives in the
  DisputeGameFactory gameArgs (clones-with-immutable-args), so absolutePrestate()
  on the bare implementation returns zero. Read gameArgs(<gameType>) or a created
  game instance instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
…000)

* docs(chain-operators): add kona-client custom prestate tutorial

New tutorial under chain-operators/tutorials/ covering the
custom-configuration kona-client prestate workflow for chains not yet
in the public superchain-registry. Mirrors the structure of the existing
absolute-prestate.mdx tutorial (which covers op-program / game type 1)
but documents the kona-registry KONA_CUSTOM_CONFIGS_DIR mechanism for
game type 8 (CANNON_KONA).

The tutorial is intentionally a narrow public-facing reference:
- Schema details for chainList.json + configs.json link out to the
  kona-registry README on GitHub (public).
- An Info callout makes clear that most chains are in the SR and should
  not follow this path.
- Steps cover branching off a kona-node release tag, staging configs,
  wiring the rust/justfile recipe, building, verifying reproducibility,
  and the deploy + op-challenger configuration.

This tutorial will be linked from the U19 notice's
"For chains with custom chain configurations" subsection (separate
branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* docs(chain-operators): refocus absolute-prestate tutorial on kona-client

Following Upgrade 19's promotion of CANNON_KONA (game type 8) as the
respected game type for permissionless fault proofs and the
deprecation of op-program for new fault proofs, this tutorial is
rewritten to cover the canonical kona-client flow.

- Standard prestate sourced from superchain-registry's
  cannon64-kona entries in standard-prestates.toml.
- Build/verify steps use `just reproducible-prestate-kona` from the
  monorepo root at the kona-node/v<VERSION> tag.
- Preimage output path updated to rust/kona/prestate-artifacts-cannon/
  (vs op-program/bin/ in the previous version).
- op-challenger flags switched to the cannon-kona variants:
  OP_CHALLENGER_CANNON_KONA_PRESTATES_URL,
  OP_CHALLENGER_CANNON_KONA_ROLLUP_CONFIG,
  OP_CHALLENGER_CANNON_KONA_L2_GENESIS.
- Custom-config section is a brief stub linking to the new
  /chain-operators/tutorials/kona-custom-prestate tutorial for the
  full unannounced-chain flow.
- A short retention note at the bottom points chains still resolving
  in-flight CANNON games to the archived Upgrade 18 notice.

URL slug preserved (no redirects needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* docs(chain-operators): archive op-program prestate tutorial

Preserves the pre-Upgrade-19 op-program absolute prestate flow at
chain-operators/tutorials/archive/op-program-prestate.mdx, with a
prominent Warning callout at the top noting the page is archived and
pointing readers at the active kona tutorials.

This is what the active /chain-operators/tutorials/absolute-prestate
"op-program (legacy)" section now links to, instead of the archived
Upgrade 18 notice.

Nav: adds an "Archive" group under Chain Operators → Tutorials,
mirroring the notices/archive structure.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* docs(chain-operators): trim absolute-prestate tutorial to minimal reproducer

Per review: the previous rewrite carried over a lot of conceptual
prose, alternative-config callouts, and a full Docker run snippet that
weren't strictly needed to reproduce a prestate. Cuts those.

The remaining content is the minimum required to:
1. Get from `git clone` to a built kona-client prestate
2. Verify the hash against standard-prestates.toml
3. Wire op-challenger to fetch the resulting binary

Links to the custom-config tutorial and the archived op-program tutorial
remain in the top callout for readers who need those paths.

116 lines removed, 22 added; total 159 -> 65.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* docs(chain-operators): polish kona prestate tutorials

- absolute-prestate.mdx: drop the op-program-archive link from the top
  callout (kept only the custom-config redirect — operators who care
  about op-program will land via the upgrade-19 notice or the legacy
  page's own Warning callout); trim op-challenger configure block to
  the two env vars that actually change for kona, since rollup-config /
  l2-genesis / game-factory-address are inherited from the existing
  challenger setup.
- kona-custom-prestate.mdx: inline chainList.json + configs.json
  templates with placeholders so a reader can copy-paste a working
  starting point without going to the kona-registry README; note that
  out-of-the-box KONA_CUSTOM_CONFIGS_DIR support needs kona-node/v1.5.2+
  (older tags require an extra justfile patch).
- docs.json: move the Archive group out of Tutorials and to the same
  level as Tutorials/Guides/Reference under Chain Operators, so the
  archive doesn't visually interrupt the active tutorial list.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* docs(kona-prestate): fix prestate-in-gameArgs + add embedded-config check

- Correct the registration step: on v2.4+ dispute-game contracts the absolute
  prestate is NOT a constructor immutable — it lives in DisputeGameFactory
  gameArgs (clones-with-immutable-args). You reuse the existing permissionless
  FaultDisputeGame impl with new gameArgs (no fresh deploy per prestate). Show
  the 124-byte packed gameArgs, the 3-arg setImplementation + setInitBond, and
  AnchorStateRegistry.setRespectedGameType(8). Note the pre-v2.4 behavior.
- Add an "embedded-config" verification step: the build log must list the
  merged chains and `gunzip | strings | grep <chain>` must match once per chain
  in chainList.json — catches a silently-dropped chain that otherwise yields the
  standard prestate hash.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* Update op-program-prestate.mdx

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Co-authored-by: Zak Ayesh <[email protected]>
…21195)

* feat(op-reth): multi-endpoint quorum validation for interop filter

Fan out interop_checkAccessList to multiple interop-filter endpoints
concurrently inside InteropFilterClient and combine by quorum agreement:
a definitive verdict (valid, or a known SuperchainDAError rejection) counts
toward quorum; transport errors and timeouts do not. The aggregator decides
once min_responses verdicts arrive and does not await slower endpoints.
Accept iff all collected verdicts are valid; all-invalid surfaces the real
reason; a mix logs and rejects (Disagreement); too few definitive verdicts
fail closed (QuorumNotReached). A failsafe reported by any endpoint is a hard
short-circuit rejection regardless of quorum.

query_failsafe ORs admin_getFailsafeEnabled across endpoints, short-circuiting
on the first true so a slow endpoint cannot delay the failsafe poll.

CLI: --rollup.interop-http is now repeatable and --rollup.interop-min-responses
sets the quorum (defaults to the endpoint count, i.e. unanimity / fail-closed).
Single-endpoint configs behave as before. Updates OpPoolBuilder and the
op-rbuilder callers for the new with_interop signature.

* feat(op-reth): detect interop failsafe by dedicated RPC code -320602

Switch failsafe detection in from_json_rpc from matching the error message
to the dedicated JSON-RPC error code -320602 the op-interop-filter server
emits, replacing the previously overloaded -32602. Detection is now by code
only, never by message text. The query_failsafe OR poll and the
check_access_list hard short-circuit on any-endpoint failsafe are unchanged.

This is a hard cutover: detection requires the filter to emit -320602, a
prerequisite server change that must deploy first.

* feat(op-reth): a received failsafe beats a met quorum, deterministically

When the valid+invalid quorum is reached, non-blocking-drain the endpoint
futures that are already ready before returning Ok, and reject if any is
FailsafeEnabled. This makes "a failsafe that has arrived rejects even when the
minimum valids are in hand" deterministic instead of arrival-order dependent.
Endpoints that have not responded are still never awaited (fast-accept is
preserved); dropping the futures cancels them.

Make failsafe_on_any_endpoint_hard_rejects deterministic by requiring all
endpoints to resolve before the decision, and add a test proving a slow
non-failsafe endpoint does not delay accept.

* fix(op-reth): classify filter verdicts — definitive rejections vs soft out-of-sync

Two related classification fixes for the multi-endpoint interop aggregator.

Definitive rejections: a fabricated access list entry is rejected by the filter
with a generic -32602 "failed to parse access entry" (not a SuperchainDAError
code). is_definitive_invalid only counted InvalidEntry, so this legitimate
rejection was mis-classified as a non-response — with a single endpoint (min=1)
the aggregator collected 0 definitive verdicts and returned QuorumNotReached
instead of the filter's real rejection, regressing
TestInteropFilter_IngressRejectsInvalid. Now any JSON-RPC error response from
the filter is a definitive verdict (new Rejected { code, message } variant,
preserving the real reason) except the JSON-RPC internal error -32603, which
stays a non-response.

Soft out-of-sync failures: codes meaning "this node does not have the data yet"
— FutureData (-321401) and Uninitialized (-320400) — are soft, non-response
failures (new DataUnavailable variant; neither definitive_invalid nor failsafe).
A single out-of-sync node is ignored as long as the quorum is met by other
nodes; if every node is out of sync the aggregator fails closed
(QuorumNotReached). FutureData is a SuperchainDAError variant, so the soft check
precedes the SuperchainDAError mapping.

Classification order in from_json_rpc: no error response → Other (transport);
-320602 → FailsafeEnabled; (-321401|-320400) → DataUnavailable; SuperchainDAError
→ InvalidEntry; -32603 → Other; else → Rejected.

Verified with the op-acceptance-tests interop/filter suite:
TestInteropFilter_IngressRejectsInvalid and TestInteropFilter_FailsafeLifecycle
both pass.

* fix(op-reth): address interop-filter multi-endpoint review feedback

- query_failsafe no longer clears the failsafe gate on partial replies:
  clearing to false now requires every endpoint to reply false; partial
  info leaves the cached state unchanged (a silent endpoint may itself be
  in failsafe). Turning the gate on still short-circuits on the first true.
- A failsafe detected during a check flips the cached gate/gauge immediately
  (reject_failsafe) so admission stops and the dashboard reflects it without
  waiting for the next ~1s poll.
- Per-endpoint metrics labeled by endpoint index (EndpointMetrics): latency,
  valid/invalid/unavailable, so "which endpoint is slow/down/dissenting?" is
  answerable. Labeled by index, never the raw URL (creds).
- Rate-limited info log when a check fails closed on an unmet quorum, plus a
  startup info log of the effective min_responses / endpoint count, so a
  degraded-quorum interop halt is visible beyond the counter.
- Spell out in --rollup.interop-min-responses help that the unanimity default
  means any single unreachable/out-of-sync endpoint blocks all interop
  admission, and suggest a majority quorum to tolerate a degraded endpoint.

* fix(op-reth): correct intra-doc link to InteropFilterClient

The crate::InteropFilterClient path does not resolve (the type is
re-exported from the interop_filter module, not the crate root). Use the
in-scope bracketed name so rustdoc -D warnings passes.
* feat(op-chain-ops): add check-interop smoke-test CLI

A small CLI under op-chain-ops/cmd/check-interop for smoke-testing interop
cross-chain messaging against a running pair of L2 chains.

Subcommands:
- roundtrip: bridges ETH A->B and B->A via the SuperchainETHBridge predeploy,
  relaying each message itself through L2ToL2CrossDomainMessenger.relayMessage,
  and asserts the recipient is credited the exact amount on each destination
  chain. No contract deploys — it uses the predeploys that already exist.
- failsafe: verifies the op-interop-filter failsafe lifecycle via the filter
  admin RPC — a bridge succeeds, its relay is rejected while failsafe is
  enabled, and succeeds again once disabled.

Flags can be supplied via CLI, CHECK_INTEROP_* env vars, or a TOML config file
(--config); CLI and env values override the file. Endpoints can be any reachable
RPC URL (e.g. a devnet's direct ingress), so no port-forward is required.

* feat(op-chain-ops): add --iterations to check-interop roundtrip

The roundtrip command now repeats the A->B / B->A cycle N times (default 3)
via a new --iterations flag (env CHECK_INTEROP_ITERATIONS, config key
"iterations"). The flag is registered only on the roundtrip command. Each
iteration runs both legs and stops on the first failure, wrapping the error
with "iteration i/N" for context.

* test(op-chain-ops): address check-interop review feedback

- randomRecipient: surface rand.Read errors via panic instead of
  discarding with blank `_` (repo baseline disallows blank error returns;
  crypto/rand failure is not expected)
- add TestInteropTxRejected table test covering all recognized interop
  rejection substrings, a wrapped-error case, and negative cases
- config.example.toml: group keys under which subcommand uses them
…ders (#21287)

Create op-core/params, the OP-Stack chain config layer, ported from op-geth.
Additive only: no call sites are swapped yet.

- OptimismConfig: verbatim JSON tags from op-geth (the value is serialised as
  rollup.Config.ChainOpConfig, so the wire format must stay identical).
- ChainConfig: a standalone type carrying the OP hardfork schedule. It does NOT
  embed go-ethereum's params.ChainConfig, so it cannot promote go-ethereum's
  OP-unaware EIP-1559 methods (ElasticityMultiplier, BaseFeeChangeDenominator) and
  return L1 defaults — there is no silent-wrong-default footgun. Fork predicates
  use a single ActivationTime / SetActivationTime switch pair with IsForkActive-
  delegating Is<Fork> methods, matching rollup.Config (tracked in #21267).
- GethChainConfig: converts to the equivalent go-ethereum params.ChainConfig for
  code that drives a go-ethereum EVM, genesis, or block processor; its output
  mirrors op-geth's LoadOPStackChainConfig.
- LoadChainConfig / LoadChainConfigFromChainID: replace op-geth's
  LoadOPStackChainConfig. The by-chain-ID loader lives here, not in
  op-core/superchain, so op-core/params -> op-core/superchain stays one-way and the
  two packages never form an import cycle. The zero-caller op-core/superchain stub
  that delegated to op-geth is removed.

A registry-wide differential test asserts GethChainConfig reproduces op-geth's
LoadOPStackChainConfig byte-for-byte across every embedded chain.

Update docs/ai/opgeth-decoupling.md (sections 5-7, summary, and a new section 14)
to the standalone + GethChainConfig design and the genesis-tooling migration path.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
… releases index (#21280)

Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
Fixes ethereum-optimism/optimism-private#552. The OP fork chronology is
Jovian < Karst < Lagoon(Interop), so Interop is the newest fork.

Fix 1 (kona-genesis): RollupConfig::spec_id jumped from HOLOCENE straight
to FJORD with no GRANITE branch, so Granite-era (post-Granite, pre-Holocene)
blocks wrongly resolved to FJORD. This diverges from op-geth, which enforces
the restricted bn256Pairing precompile (input limit 112687) at
IsOptimismGranite, and from op-revm, which selects it only for GRANITE. Add
the missing GRANITE branch between HOLOCENE and FJORD.

Fix 2 (op-revm): into_eth_spec mapped the newer INTEROP to PRAGUE while the
older KARST mapped to OSAKA, i.e. a downgrade of the eth base for a newer
fork. op-geth decouples the eth base via independent OsakaTime/PragueTime, so
a post-Interop block's eth base should be Osaka (matching Karst). Map
INTEROP to OSAKA alongside KARST.

Fix 3 (alloy-op-evm): spec_by_timestamp_after_bedrock checked Karst before
Lagoon/Interop, returning KARST for post-Lagoon blocks. Reorder to check
Lagoon/Interop first (newest-first), matching kona's correct ordering.

Add conformance guards in op-revm and alloy-op-evm locking in the
newest-first fork ordering and eth-base monotonicity (a newer OP fork must
never map to an older eth base).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* chore: remove op-program from the monorepo

op-program is no longer used: op-e2e/op-devstack were decoupled
(ethereum-optimism/optimism#21254, #21257) and op-challenger no longer requires
the op-program server binary for the permissioned game type, while the legacy
Cannon game type (0) is no longer in use. Kona is the in-tree fault-proof
program in production.

- Delete the op-program/ directory.
- Remove op-program build/test/prestate targets from justfile, Makefile,
  op-e2e/justfile, and the err113 lint exception from .golangci.yaml.
- Remove op-program CircleCI jobs (sanitize-op-program, analyze-op-program-client,
  op-program-compat, publish-cannon-prestates, preimage-reproducibility), their
  workflow wiring, the op-program/v.* release-tag prestate publishing, and the
  now-dead preimage-reproducibility scheduled pipeline plumbing.
- go mod tidy (no transitive dep changes).

GitHub Actions image config (.github/images.json) is intentionally left
untouched. Completes ethereum-optimism/optimism#20276.

* chore: drop stale op-program reference from rust/justfile comment

* chore: fix dangling cannon-prestates refs after op-program removal

The op-program removal deleted the root justfile cannon-prestates recipe,
but two callers still referenced it. Acceptance tests now consume the kona
prestate (via CannonKona game types and rust/kona/prestate-artifacts-cannon),
so point op-acceptance-tests build-deps at just build-kona-prestates, and drop
the now-dead cannon-prestates deprecated shim from op-e2e/Makefile.

* chore: restore cannon-prestates as a kona-only recipe and update docs

- Re-add `just cannon-prestates` (and keep `just reproducible-prestate`) building
  only the kona prestates, so existing callers keep working after op-program removal.
- Remove op-program references from docs: README/AGENTS component listings,
  op-node/op-preimage READMEs, the acceptance-tests prestate note, and rewrite the
  cannon and op-challenger README examples to use kona (kona-host / cannon-kona).
- Delete the vm-compat-triage skill, which triaged the now-removed
  analyze-op-program-client CI job.

* Fix comment

Co-authored-by: Adrian Sutton <[email protected]>

* chore: keep prestate reproducibility check, move it out of op-program

The preimage-reproducibility CI job verifies the reproducibility of every
released cannon prestate — both op-program (op-program/v* tags) and kona
(kona-client/v* tags, including legacy) — against the superchain-registry
standard prestates. It must be kept after op-program removal: it still covers
kona, and historical op-program prestates are rebuilt from their own
checked-out release tags (which retain op-program), so that coverage is
preserved too.

- Move the driving scripts out of the deleted op-program tree to
  ops/prestate-reproducibility/ (build-prestates.sh + the prestates verify Go
  package), fixing the import path and temp-dir locations.
- Add a `verify-reproducibility` recipe to the root justfile.
- Restore the preimage-reproducibility CircleCI job (now run from the repo root
  via `just verify-reproducibility`), the scheduled-preimage-reproducibility
  workflow, and its dispatch plumbing.
- Point op-acceptance-tests build-deps back at the generic `just cannon-prestates`.

* chore(op-e2e): remove obsolete engineapi conformance test suite

TestEngineAPITests ran the engineapi/test RunEngineAPITests suite, which was a
factory-parameterized conformance suite originally used to prove the op-program
engine backend matched op-geth. With op-program removed there is only one
backend left, so the parity motivation is gone and the suite only re-exercises
op-geth behavior already covered elsewhere. Remove the test and the
engineapi/test subpackage (its sole consumer). The main engineapi package stays
— it is the in-process L2 engine used by the action-test framework.

* chore(op-challenger): drop op-program from the op-challenger docker image (#21284)

* chore(op-challenger): drop op-program from the op-challenger docker image

The op-challenger image bundled the op-program binary as the legacy cannon
server (OP_CHALLENGER_CANNON_SERVER). With op-program removed from the monorepo
the COPY from the op-program-builder stage fails, breaking the image build.
Drop the op-program COPY and the OP_CHALLENGER_CANNON_SERVER env. The image
still ships cannon and kona-host (OP_CHALLENGER_CANNON_KONA_SERVER /
OP_CHALLENGER_ASTERISC_KONA_SERVER), which is what production fault proofs use;
the legacy cannon game type (0) is no longer in use and the permissioned game
type never runs a fault-proof server.

* chore: remove the op-program docker image (#21285)

op-program has been removed from the monorepo, so the op-program docker image
can no longer be built. Remove it everywhere it's defined:

- .github/images.json: drop the op-program image entry, and the stale
  op-program/** path glob from the op-challenger image's trigger paths.
- docker-bake.hcl: remove the op-program bake target and the now-unused
  OP_PROGRAM_VERSION variable.
- ops/docker/op-stack-go/Dockerfile: remove the op-program-builder and
  op-program-target build stages.

The op-challenger image already stopped referencing op-program in the previous
stacked PR.
* op-alloy: reject zero elasticity in eip1559 params decoding

Following up on #18625, also catch zero elasticities during EIP-1559
params decoding. Catching this at the op-alloy level makes decoding
fail early, mirroring the Go validation in op-geth and op-reth#20858.

`decode_holocene_extra_data` and `decode_jovian_extra_data` now reject
a zero denominator (`ZeroDenominator`) and a zero elasticity
(`ZeroElasticity`) in the block-header extraData, matching the Go
`validateHoloceneExtraDataPart` rule that both values must be non-zero.

Since the zero-denominator (and now zero-elasticity) case is caught
early in op-alloy, the redundant `denominator == 0` checks and the
now-unused `Eip1559ValidationError::ZeroDenominator` variant are
removed from kona-executor.

Closes #18951

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* op-reth: drop unreachable both-zero base-fee fallback

With #18951, decode_holocene_extra_data / decode_jovian_extra_data reject a zero
denominator or elasticity, making the both-zero fallback to chain-default base-fee
params in decode_holocene_base_fee / compute_jovian_base_fee unreachable. Remove it
and the now-unused chain_spec/timestamp parameters.

Refs #18951

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* op-alloy: drop private intra-doc links in eip1559 docs

The `validate_extra_data_eip_1559_params` references in the public
decode_holocene_extra_data / decode_jovian_extra_data docs link to a private
helper, which fails `rustdoc::private_intra_doc_links` under `-D warnings`
(rust-docs CI). The docs already state the non-zero rule, so drop the references.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* op-alloy/op-reth: address review — consensus base-fee tests + nits

- reth-optimism-consensus: 4 base-fee tests fed raw (0,0) eip1559 params and
  asserted the old translate-to-defaults result, which now returns None since a
  (0,0) header is invalid per Holocene. The holocene extra_data_not_set test now
  asserts None; the three Jovian min-base-fee tests encode the chain defaults
  (as a real header does) so they keep exercising the min-base-fee floor.
- op-alloy: order validate_extra_data_eip_1559_params args as
  (elasticity, denominator) to match decode_eip_1559_params; reword the Jovian
  decoder doc to 'inherits the Holocene header rules'.

Refs #18951

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* kona-engine: update attributes eip1559 tests for stricter decode

The op-alloy change rejects a zero denominator/elasticity in header extraData,
so two attributes tests that fed an all-zero (0, 0) block header and asserted
the old translate-to-(0, 0) value mismatch now fail at decode instead. Both now
assert UnknownExtraDataDecodingError(ZeroDenominator), matching the sibling test
that does so for InvalidExtraDataLength. Drop the now-unused EIP1559Parameters
import.

Refs #18951

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
nonsense and others added 26 commits June 23, 2026 08:36
…(#21515)

Adds a MixedL2ELOpRethPremium EL kind that boots op-reth-premium in place
of op-reth. The premium binary uses reth_optimism_cli::Cli (the same CLI
as op-reth, with the proofs subcommands) plus an additive --subblocks.*
namespace, so it is a genuine CLI superset and reuses the existing
buildMixedOpRethNode invocation unchanged — only the resolved binary
differs (new OpRethWithBinary option / OpRethConfig.Binary). Wired into
both EL dispatch sites (NewMixedSingleChainRuntime and the env-driven
startL2ELForKey, so DEVSTACK_L2EL_KIND=op-reth-premium also works).

op-reth-premium lives in a separate repo, so it is resolved via the
existing rustbin env overrides (RUST_BINARY_PATH_OP_RETH_PREMIUM, or
RUST_SRC_DIR_OP_RETH_PREMIUM + RUST_JIT_BUILD).

Adds TestPremiumSequencerProducesDerivableBlocks: a baseline regression
guard that boots a premium sequencer (subblocks producer, SDM off) with a
stock op-reth verifier and asserts both unsafe heads advance — i.e.
premium produces derivable blocks as a drop-in. It does NOT assert SDM /
0x7D behavior (premium does not produce PostExec blocks yet). The test
skips unless the premium binary is provided, so it is inert in CI without
a premium image and never fails there.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
… state-root mismatch (#21495)

* fix(op-reth): surface pruned block bodies as a typed error instead of state-root mismatch

* ci fixes

* op-rbuilder fixes

* bump rollup-boost cargo

* review fixes

* revert op-rbuilder change

* revert space changes
…pt-in TOCTOU) (#21502)

* fix(sdm): harden post-exec payload handling

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.

* fix(op-reth): sync op-rbuilder and rollup-boost lockfiles

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) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
The report cover page identifies it as a Cantina Managed review, not
Spearbit. Rename the PDF and update the ledger row to match.
…/fees (#21508)

* op-core: wire op-node onto op-core/eip1559 and txinclude onto op-core/fees

Switch op-node's eip1559 consumers and op-service/txinclude's fee consumers off
op-geth onto the op-core packages, completing the eip1559 + fees parts of #20271
(sub-issue #21500). The superchain part landed separately in #21487.

eip1559: swap the import path in payload_util.go, engine_consolidate.go,
system_config.go (+ engine_consolidate_test.go) to op-core/eip1559, and add the
ValidateOptimismExtraData helper the initial extraction omitted (+ test).

fees: in txbudget.go and cost_oracle.go, replace types.NewL1CostFuncFjord and
tx.RollupCostData() with the op-core/fees equivalents. Simplify the op-core/fees
L1-cost API to free functions (L1CostEcotone/L1CostFjord taking an L1FeeParams
struct), consistent with the existing L1CostBedrock — dropping the closure
factories, the L1CostFunc type, and op-geth's vestigial calldataGasUsed return
and blockTime arg, none of which any monorepo consumer needs. Add a live
differential test pinning L1CostFjord to op-geth's NewL1CostFuncFjord.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* op-core/eip1559: dedupe ValidateOptimismExtraData dispatch test

Trim TestValidateOptimismExtraData to cover only fork dispatch — each per-fork
validator's edge cases stay in its own test (TestValidateHoloceneExtraData),
removing the overlap a reviewer flagged.

Keep ValidateHoloceneExtraData / ValidateJovianExtraData public rather than
folding them behind ValidateOptimismExtraData: with the whole monorepo in scope
for the op-geth decoupling, op-chain-ops/check-jovian (an ethclient checker that
stays in Go, not block-building) is a real direct consumer of
ValidateJovianExtraData.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* op-sync-tester: swap eip1559 onto op-core/eip1559

op-sync-tester validates header extraData and PayloadAttributes 1559 params via
the eip1559 helpers; point them at op-core/eip1559 (pure rename, the functions
already match). Removing op-sync-tester's remaining op-geth coupling — notably
the miner.BuildPayloadArgs.Id() PayloadID hash — is tracked in #21525.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(ai): widen opgeth-decoupling scope to the whole monorepo

Replace the "in scope / out of scope" subset with the three-fates framing
(swap-to-op-core / migrate-execution-to-op-reth / delete), and surface two
components the plan had missed: op-sync-tester (a CL-sync EL mock that does no
execution — a swap target, with its OP-aware PayloadID hash tracked in #21525)
and the op-chain-ops/cmd/check-* per-fork checker family.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(ai): drop op-program/op-supervisor from opgeth-decoupling doc

Both have been removed from the monorepo (op-program superseded by rust/kona,
op-supervisor by op-supernode), so the "replaced, not decoupled" note, the §12
out-of-scope subsection, and the §14 execution-engine reference to them are
stale. Per PR review.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Bumps Go module dependencies to clear Grype findings on current develop:

- golang.org/x/crypto v0.46.0 -> v0.53.0
- golang.org/x/net v0.48.0 -> v0.56.0
- golang.org/x/sys v0.40.0 -> v0.46.0
- golang.org/x/image v0.25.0 -> v0.43.0
- go.opentelemetry.io/otel{,/trace,/metric} v1.40.0 -> v1.44.0

Related golang.org/x modules updated by the solver: x/mod, x/sync, x/term,
x/text, x/tools, and x/telemetry.

Leaves quic-go/webtransport-go for a separate libp2p upgrade, and leaves the
btcd scanner finding untouched because the latest btcd module split breaks
existing btcutil import paths.
* cleanup(contracts): rename super permissioned game type

* review comments
* fix(op-rbuilder): gate interop txs during failsafe

* fix(op-rbuilder): remove interop feature gate
…ns) (#21441)

* fix(derive): keep upgrade gas on a NUT-bundle fork's activation block only

Karst introduced "upgrade gas": extra gas added to a NUT-bundle fork's
activation block gas limit so the network-upgrade transactions fit. It is meant
to apply only to the activation block, but it stuck to every later block — the
SystemConfig gas limit is reconstructed from the parent header (op-node
PayloadToSystemConfig / kona to_system_config), so the inflated activation-block
limit was read back as the steady-state limit indefinitely. op-node and kona
shared the bug, so they agreed but were both wrong.

Subtract the upgrade gas again at the block right after the activation block,
for every NUT-bundle fork from Karst onward (forks.From(forks.Karst) in Go,
OpHardfork::Karst.forks_from() in Rust), so future forks are covered
automatically. A new rollup-config bool KeepKarstUpgradeGas /
keep_karst_upgrade_gas (default false) lets chains that already activated Karst
with the leak baked into their history opt out of the Karst subtraction only, so
their history still validates; the operator clears it later with setGasLimit.
The strip runs during system-config reconstruction, before
UpdateSystemConfigWithL1Receipts / update_with_receipts, so a setGasLimit in the
post-activation block's L1 origin still takes precedence.

Also syncs the superchain-registry submodule to the companion change
(ethereum-optimism/superchain-registry#1259), which enables
keep_karst_upgrade_gas per-chain for the affected sepolia and mainnet chains,
and regenerates the Go + Rust embeddings via `just sync-superchain`. Migrates the
op-node derive test `ptr` helper to the shared op-service/ptr.New.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* derive: address upgrade-gas PR review feedback

Addresses review comments on #21441.

op-node:
- Simplify upgradeGasToStrip to a one-fork-per-block early return.
- Reword the strip doc/inline comments ("Starting with Karst", "subtract it
  back here").
- Tests: activate Jovian at genesis, fix an assertion message, parameterize
  TestUpgradeGasToStrip over forks.From(Karst), and drive the
  setGasLimit-precedence case through PreparePayloadAttributes instead of
  duplicating the strip/update ordering.
- Print keep_karst_upgrade_gas at startup when set, and add the
  --override.keep-karst-upgrade-gas flag.

kona:
- Extract a public upgrade_gas(fork) getter with a VARIANTS guard test
  (OpHardfork is #[non_exhaustive], so a missing fork can't fail compilation).
- Early-return simplification mirroring op-node; reword the strip comment.
- HardForkConfig::Display prints keep_karst_upgrade_gas when set; move its
  comment between Karst and Lagoon.
- Add the --keep-karst-upgrade-gas-override flag to kona-node and kona-cli.
- Action test: prove the post-activation block too.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* kona test: prove post-activation block in NUT-bundle action test

Run the fault-proof program over the post-activation block too, so kona-client
verifies the gas-limit revert directly rather than relying only on op-node's
assertion. Addresses review feedback on #21441.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* superchain: re-pin registry to superchain-registry#1259 merge commit

Bump the superchain-registry submodule to d84b53b (the #1259 merge into SR main,
adding keep_karst_upgrade_gas) and regenerate the Go + Rust embeddings via
`just sync-superchain`. superchain-registry#1263 removed creator-chain-testnet
and ethernity, so only lisk and the OP-stack chains carry the flag now.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* derive: address tuicr review feedback

- payload_util_test: use op-service/ptr.New instead of a local minBaseFee const.
- kona: extract a RollupConfig::is_fork_activation_block helper (mirrors op-node's
  IsActivationBlockForFork) and use it in upgrade_gas_to_strip.
- kona: fix the Lagoon override env var (KONA[_NODE]_OVERRIDE_INTEROP ->
  KONA[_NODE]_OVERRIDE_LAGOON), a leftover from the Interop -> Lagoon rename.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* rust: refresh SP1 guest Cargo.lock

kona-protocol depends on alloy-op-hardforks and kona-hardforks (the upgrade_gas logic); the SP1 guest workspace path-depends on kona-protocol, so its separate lockfile needs those entries. Regenerated via `just lock-sp1-guest`.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(ci): make op-reth-compact-codec resilient to a superchain-registry bump

The op-reth-compact-codec job builds op-reth on develop (to write compact
vectors) and then on the PR branch (to read them). `git checkout` switches the
tracked files between base and PR but leaves both the superchain-registry
submodule working tree and the gitignored res/superchain-configs.tar at the
other side's content. reth-optimism-chainspec's build.rs (superchain-configs is
a hard, unconditional dep of the op-reth bin) regenerates/validates that tar
against the committed .sha256, so on a PR that bumps the submodule it panics on
a base-vs-PR mismatch.

After each checkout, re-sync the submodule to the checked-out pin
(`just update-superchain-registry-submodule`) and drop the stale gitignored tar
so build.rs regenerates it from the now-current submodule.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* derive: address further PR review feedback

- kona to_system_config / upgrade_gas_to_strip tests: activate all forks through
  Jovian at genesis via a forks_through_jovian_at_genesis() helper, matching the
  op-node test's fork schedule (geoknee).
- kona NUT-bundle action test: prove the activation-block span before building the
  post-activation block (in addition to the post-activation span), and refresh the
  stale Lagoon early-return comment (drop the closed-#21114 reference and the
  outdated "op-node strips just Karst" note).
- docs/ai/fault-proofs.md: call out that these tests cover op-node (which sequences
  and derives the chain) as well as kona-client (which proves it).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* genesis: add modular HardForkConfig fork accessors + activate_at_genesis

Replaces the ad-hoc test helper with a proper API on HardForkConfig, mirroring
op-node's Config.ActivateAtGenesis: OpHardfork-keyed fork_time / set_fork_time
accessors and activate_at_genesis(fork), which iterates the fork sequence setting
each to genesis up to and including the target. The kona to_system_config and
upgrade_gas_to_strip tests use it (via a thin karst_at helper) so their fork
schedule matches the op-node test.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* karst: add keep_karst_upgrade_gas action-test variant + deploy-config plumbing

Adds TestKarstActivationKeepUpgradeGas to the NUT-bundle action test, covering the
keep_karst_upgrade_gas opt-out (the other branch of testActivationBlockNUTBundle's
revert assertion): the inflated activation-block gas limit must persist on later
blocks, and kona-client must agree.

The flag is set the same way as fork timestamps — via the deploy config: a new
KeepKarstUpgradeGas field on UpgradeScheduleDeployConfig flows into rollup.Config
through DeployConfig.RollupConfig(), and activateFork takes a keep parameter that
sets it. This bakes the opt-out into the whole chain (sequencer, derivation, fault
proof) from genesis, rather than mutating the live rollup config mid-test.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* e2eutils: propagate keep_karst_upgrade_gas into the action-test rollup config

e2eutils.Setup builds rollup.Config by hand and wasn't copying the deploy config's
KeepKarstUpgradeGas, so the kona fault-proof action test's keep variant
(TestKarstActivationKeepUpgradeGas) saw the default and reverted the gas limit. Copy
the field, alongside the fork-time fields it sits next to.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* karst: assert exact activation-block gas limit in the NUT-bundle action tests

Check the activation block's gas limit is exactly preActivation + UpgradeGas(fork)
rather than merely greater, in both the revert and keep variants. Verified locally:
TestActivationBlockNUTBundle and TestKarstActivationKeepUpgradeGas both pass under a
freshly built kona-host (revert + keep, fault proofs included).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…stall (#21532)

Follow-up to #21489. The ci-base-clang image now ships protobuf-compiler +
libprotobuf-dev (the sp1-sdk prost/tonic build dep), so the per-job apt-get
fallback install added in that PR is no longer needed.

- Pin the rebuilt ci-base-clang digest (sha256:873a6709…, attested by the
  build-images workflow on develop; provenance verified by security.yml).
  This also realigns the c-rust_base_image fragment defaults, which had
  drifted to an older digest (758f67c7…) since the last manual bump (#21361)
  only updated config.yml + main.yml's rust_base_image.
- Replace the protoc apt-get install in rust-prepare with a clang-style
  presence check that fails fast if protoc / the protobuf well-known type
  includes are missing from the image.

Validated: merge-configs.sh, circleci config validate (orb stubbed), and
test-decision-tree.sh (18/0).

Co-authored-by: wwared <[email protected]>
* feat(op-node): remove deprecated Req/Resp CL P2P sync client

Removes the client side of the deprecated Req/Resp CL P2P sync protocol
from op-node, while keeping the serving side (ReqRespServer) intact. This
is the first of two changes: serving payloads to peers that still request
them lets nodes on older versions keep syncing against upgraded nodes
during a rolling upgrade. The server is removed in a follow-up.

- Delete the SyncClient (and all client-only helpers) from
  op-node/p2p/sync.go; the file now contains only ReqRespServer and the
  shared stream/protocol helpers it needs.
- Strip client activation/wiring from NodeP2P: the req-resp serving side
  is still registered behind `p2p.sync.req-resp` (+ a data-source), but
  the client peer-tracking, RequestL2Range and AltSyncEnabled are gone.
- Remove the driver AltSync interface, the altSync field/param, and
  OpNode.RequestL2Range / unixTimeStale. The unsafe-queue gap-check ticker
  is kept (renamed altSync* -> unsafeGap* locals) and now always drives the
  engine-queue gap-fill path.
- Make `syncmode.req-resp` and `p2p.sync.onlyreqtostatic` hidden,
  deprecated no-op flags; delete `sync.Config.SyncModeReqResp` and simplify
  every branch that read it (sync_deriver, engine_controller) to the false
  path. Drop the cross-flag startup validation in service.go.
  `p2p.sync.req-resp` stays active and now gates only the serving side.
- Remove the client payload-by-number metrics (ClientPayloadByNumberEvent,
  PayloadsQuarantineSize) and the quarantine collector; keep the server
  metric and the shared p2p req collectors.
- Update devstack/op-e2e/op-acceptance-tests: drop the client-only
  UseReqResp(Sync) knobs (keeping EnableReqResp* which gates the server);
  delete the enabled-path depreqres suite and the e2e TestSystemP2PAltSync;
  keep a server-only TestMutexUnlocks in p2p/sync_test.go.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(op-node): address review comments on req-resp client removal

- Note that the req-resp sync server will be deprecated in a future
  release in favor of EL P2P sync, in the p2p.sync.req-resp flag usage.
- Adapt the checkForGapInUnsafeQueue comment: it no longer retrieves
  payloads from peers over the network; it re-inserts the locally
  queued unsafe payload to drive the engine-queue gap-fill.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Adds a Karst-activation acceptance test in op-acceptance-tests/tests/karst
that activates Karst mid-chain and observes the gas limit across the
pre-activation, activation, and post-activation blocks. It asserts the
activation block carries the one-time NUT-bundle upgrade gas (gas limit >
pre-activation) and that the post-activation block reverts to the
steady-state limit, covering the fix in #21441.

Two variants:
- default: post-activation gas limit reverts to the pre-activation value.
- keep_karst_upgrade_gas = true: post-activation gas limit keeps the
  inflated activation-block value.

Each variant runs the kona-client fault proof (RunKonaNative) over a span
agreeing on the pre-activation block and proving into the post-activation
block, so the fault-proof program covers the behaviour too.

Plumbs keep_karst_upgrade_gas through the deploy path so the second
variant can opt out: a KeepKarstUpgradeGas DeployConfig field flows into
rollup.Config via RollupConfig(), an intentbuilder L2 setter writes the
deploy override, and a sysgo.WithKeepKarstUpgradeGas deployer option sets
it on all L2s. Adds a small L2ELNode.GasLimitAtBlock DSL getter.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…694) (#21146)

* docs(op-stack): reflect op-reth/kona defaults in protocol explainers

Light conceptual-prose pass for the op-geth -> op-reth and op-program ->
kona-client deprecations (partial #694):

- fp-components: present kona-client as the default fault-proof program;
  op-program framed as predecessor with EOS at Karst
- transaction-flow: state execution by "the execution client (op-reth by
  default)"; op-geth noted as supported-but-EOS
- smart-contracts: MIPS-upgrade note covers kona-client (Cannon VM stays,
  client program changes)

Out of scope here (op-geth-specific operational config or historical
source links): architecture.mdx (GETH_* config -> #691), components.mdx,
fees.mdx, sync-op-mainnet.mdx.

Refs ethereum-optimism/solutions#694

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(chain-operators): convert network-design-example to op-reth

architecture.mdx (#694): replace op-geth as the default EL in prose and
convert the GETH_* config blocks to op-reth.

- txpool/netrestrict/gossip blocks -> op-reth CLI flags
  (--txpool.disable-transactions-backup/.nolocals/.lifetime, --netrestrict);
  op-reth has no GETH_*-style env vars for these (reth clap args define no
  env=; only globals like RETH_DATADIR exist)
- archive: op-reth is archive by default (no gcmode); full -> --full
- DB_ENGINE / STATE_SCHEME / snap syncmode have no op-reth equivalent
  (MDBX storage + staged sync) -> honest notes, snapshot-restore guidance
- fix pre-existing broken /operators/* monitorism link

Refs ethereum-optimism/solutions#694

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(op-stack): point EVM/tx-encoding source links to op-reth stack

#694 follow-up on the two remaining source-link pages:

- components.mdx: drop the op-geth diff-viewer link and the geth-comparison
  Specifications line; point EVM "Source code" to op-reth (rust/op-reth)
- fees.mdx: repoint the tx-RLP encoding links from op-geth's
  core/types/transaction.go to op-alloy's OpTxEnvelope
  (rust/op-alloy/.../transaction/envelope.rs); keep "standard Ethereum
  transaction RLP encoding" phrasing (accurate for user txs)

Refs ethereum-optimism/solutions#694

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(fault-proofs): frame cannon-kona as the path forward in challenger explainer

challenger.mdx (#694): the op-challenger trace-executables note presented
cannon/op-program host/kona-host as a flat list. Reframe so cannon-kona
(kona-host) is the respected game type going forward and op-program host /
the legacy cannon game type is marked end-of-support at Karst.

Refs ethereum-optimism/solutions#694

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: drop architecture.mdx from explainers PR

Defer the network-design-example op-reth retarget to the dedicated
PR #21296 (docs/network-design-op-reth) to avoid a merge conflict on
this file; #21146 keeps its protocol-explainer scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: address review on explainers migration

- smart-contracts: revert Holocene MIPS bullet to op-program (point-in-time release note)
- fees: restore pinned op-geth RLP encoding links (stable vs unpinned op-alloy/develop)
- transaction-flow: end-of-support past tense + drop redundant double-link

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: fix op-geth support tense contradiction in transaction-flow

"is also supported but reached end-of-support" was self-contradictory;
reword to past tense "was previously supported but reached".

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…1528)

* feat(kona): add kona-sp1 range-executor + SP1 execute action test

Add an SP1 execute test harness so the kona-sp1 range guest can be
exercised end-to-end by op-e2e action tests, mirroring the native
fault-proof program harness.

- New `kona-sp1-range-executor` binary: takes the native kona-host
  `single` boot inputs, generates the witness via the kona-host preimage
  server, runs the range guest in SP1 execute mode (no proving), reads the
  committed BootInfoStruct, and exits 0 (valid) / 1 (invalid claim) /
  2 (infra), matching the native fault-proof exit-code convention.
- Go wiring: rustbin.RunKonaSP1Range (shared oracle-server exec path),
  RunRangeExecutor + ProgramRunner refactor in the proofs helpers,
  RunSP1RangeProgram env method, and TestSP1RangeSimpleEmptyChain.
- just recipes (build-range-executor, action-tests-sp1) + README section.
- Add ETHDAWitnessGenerator::new()/Default and use it from the host.

The test skips unless KONA_SP1_RANGE_EXECUTOR_PATH is set and is not yet
wired into a CI job (needs the Dockerized SP1 ELF build in CI). It covers
the honest-claim path only; the host-side witness generator rejects invalid
claims before the guest runs, so invalid-claim coverage is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix zepter lint

* cargo fmt

* review comments

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* feat: add FeeVault withdrawal route setter

Allow proxy admin owners to update the fee recipient and withdrawal network atomically.

* fix: bump FeeVault NUT deployment gas limits

Increase FeeVault implementation deployment headroom so generated upgrade bundles satisfy CI gas profile checks.

* doc(ctb): add context on natspec comment

* fix: bump L2ContractsManager semver for inlined FeeVault gas limits

The FeeVault deployGasLimit bumps in Predeploys.sol are inlined at compile
time into L2ContractsManager, changing its initCodeHash. The semver-diff
check requires a version bump whenever semver-lock.json changes.

* chore: regenerate current-upgrade-bundle.json for L2CM version bump

L2ContractsManager 1.10.0 changes its compiled bytecode, which the
upgrade bundle embeds. Regenerated via `just generate-nut-bundle`.

* debug: dump callTracer for reverted activation-block txs

Surfaces the revert frame for TestActivationBlockNUTBundle failures
so karst-jovian tx 31 can be diagnosed in CI logs.

* fix(contracts): update semver-lock

* fix(deployment): bump gas limit left out during merge conflict

* test(feevault): remove debug code

* fix(feevault): use prefork state in tests which start pre-fork, and activate at runtime

* fix(feevault): preserve storage during overlay

---------

Co-authored-by: 0xCoati <[email protected]>
…(#21211)

Document SDM: block-scoped warming rebates, the protocol/operator gates,
the admin_setSdmPostExecOptIn and admin_sdmStatus RPCs, and verification.
Register the page in the Features nav.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…21566)

op-node's superchain-registry decoder dropped keep_karst_upgrade_gas: the
Go HardforkConfig struct had no field for it (BurntSushi/toml silently
ignores unknown keys) and applyHardforks never copied it. So an op-node
started with --network=op-sepolia (or op-mainnet) loaded the flag as false
even though the registry sets keep_karst_upgrade_gas = true, and would
subtract the Karst upgrade gas at the post-activation block and diverge from
canonical history. Operators had to pass --override.keep-karst-upgrade-gas
by hand.

PR #21441 wired the flag into kona (its HardForkConfig reads it from the
embedded registry) and added the op-node rollup.Config field plus the CLI
override, but never extended the Go SCR-loading path. This closes that gap:

- Add KeepKarstUpgradeGas to superchain.HardforkConfig so the TOML key is
  decoded.
- Copy it in applyHardforks alongside the fork-time fields.

Tested first: asserting KeepKarstUpgradeGas == true for the SCR-loaded
sepolia and mainnet configs in TestGetRollupConfig fails on the baseline
(expected true, actual false) and passes after the fix. The reflection-based
TestApplyHardforks is updated to handle the new non-pointer bool field.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Extract the kona-sp1 range execution logic behind a shared Rust API so tests can exercise the core without running the SP1 ELF. Keep the guest entrypoint as the SP1 IO wrapper and add a range-executor native-core mode for faster action-test coverage while preserving the full SP1 execute smoke path.
BlobStore leaned on two implicit invariants: kzg_rs's batch verifier
(which skips length checks at 0/1 blob) and an undocumented host
packing order. Move both checks local to blob_provider.rs:

* assert_eq! on the three BlobData lengths at top of From<BlobData>.
* By-hash lookup (linear scan + swap_remove) in get_and_validate_blobs,
  panicking on miss instead of silently dropping the request.
* Drop the now-dead .rev() in the constructor.

In the SP1 guest both new panics surface as "invalid proof".

Behavior note: if a host returned the correct set of blobs in wrong
order, baseline returned an empty Vec; by-hash lookup now succeeds.
Strictly more correct.

Three unit tests fail on baseline cf2ce09 and pass after the fix.

Closes #21490.
* op-devstack: add controlled lifecycle support

* review comment

* fix op-up
…on, prestate TBD (#21544)

* docs: U19 notice draft — SCR fix, component versions, deposit gas, prestate TBD

- remove OP Sepolia genesis.json workaround (SCR loading fixed in op-reth v2.3.2)
- refresh component table (op-node v1.19.1, op-reth v2.3.2, kona-node TBD,
  rollup-boost v0.7.16, op-rbuilder v0.4.9) + Engine API upgrade-order note
- EIP-7825: clarify outer L1 gas budget for deposits (_gasLimit + ~150k)
- kona-client prestate tag + hashes set to [TBD] pending finalization
- op-proposer step: warn it fails immediately if game type not updated

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — add affected-chains section for Karst gas limit bug

Lists the Sepolia chains that activated Karst with the buggy release and
have an elevated block gas limit; clarifies mainnet needs no setGasLimit()
action after activation.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — setGasLimit required for ALL chains, add procedure

keep_karst_upgrade_gas = true is set in the Superchain Registry for both
Sepolia AND mainnet (verified against SR main: mainnet/op.toml and
sepolia/op.toml), enshrining the elevated block gas limit after Karst.
All chain operators must call SystemConfig.setGasLimit() after activation.

- correct the prior (wrong) claim that mainnet needs no action
- rename section to "Gas limit: action required for all chains"
- add setGasLimit() procedure (read / set from owner / verify)

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — drop per-chain gas-limit list, keep timing note

The enumerated Sepolia chain list added no action over "all chains" and
risked going stale; collapse to a one-line timing note (Sepolia live now,
mainnet later).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — remove deposit outer-L1-gas budget note

Drop the EIP-7825 deposit L1 gas-budget paragraph per review.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — drop OP Mainnet/OP Sepolia from SCR examples list

Keep the example list illustrative; the explicit OP entries aren't needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — address review (revert prestate TBD, name affected chains)

- Revert prestate section to as-is: keep_karst_upgrade_gas preserves consensus,
  so no new prestate is required (restore kona-client/v1.6.0-rc.2 tag + hashes,
  drop the TBD Info note). Per @geoknee review.
- Gas-limit section: name the affected chains (canonical SR #1259 list) and use
  the has-flag / no-flag / already-activated conditional instead of "all chains".

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs: U19 notice — address joshklop review

- remove kona-node row (no kona-node release tied to Karst; clears last TBD)
- op-reth: note v2.3.1 is now deprecated
- upgrade-order: "break the sequencer" -> "cause downtime"
- gas-limit: note some not-yet-activated mainnet chains also set the flag

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
… under Karst (#21574)

* fix(op-reth): set tx_gas_limit_cap for Osaka so eth_estimateGas works under Karst

EIP-7825 (Osaka) caps a single transaction's gas at 2^24, and the OP Stack
inherits this cap from Karst onward (KARST/INTEROP -> OSAKA eth base). The OP
EVM-env builder never populated `cfg_env.tx_gas_limit_cap`, so reth's
`estimate.rs` fell back to the block gas limit (e.g. 30-60M) as the trial upper
bound for `eth_estimateGas`. The Osaka EVM then rejected that trial with
"intrinsic gas too high", breaking any contract-call estimate that omitted an
explicit `gas <= 2^24`. This is the OP-side dual of reth PR #25612.

Set the cap in `evm_env_for_op` (the chokepoint for the block, next-block, and
payload env constructors), mirroring alloy-evm's `for_eth_block`. This is
forward-compatible: once reth is bumped past #25612 the field-set becomes
harmless belt-and-suspenders.

Add a chronology-driven unit guard in alloy-op-evm asserting the cap is set
exactly for Osaka+ forks, plus an op-reth integration test that drives a real
`eth_estimateGas` RPC end-to-end on a Karst-active chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix: satisfy clippy::if_then_some_else_none in tx_gas_limit_cap guard test

Use `.then_some(...)` instead of an if/else returning Some/None. CI runs clippy
with `--all-targets`, which lints `#[cfg(test)]` code; the local `-p alloy-op-evm`
run did not.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* refactor

* fix(op-reth): add backticks to satisfy clippy::doc_markdown

The estimate_gas_7825.rs module doc referenced eth_estimateGas without
backticks, which clippy::doc_markdown rejects under -D warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* Add TODO

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: geoknee <[email protected]>
@0byteme
0byteme merged commit 6ba481b into main Jul 1, 2026
3 checks passed
@0byteme
0byteme deleted the merge-v2.3.3 branch July 1, 2026 06:56
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.