Skip to content

Sync upstream version v1.17.0#237

Merged
KamiD merged 117 commits intodevfrom
sync-1170
Apr 30, 2026
Merged

Sync upstream version v1.17.0#237
KamiD merged 117 commits intodevfrom
sync-1170

Conversation

@louisliu2048
Copy link
Copy Markdown

No description provided.

smartcontracts and others added 30 commits April 16, 2026 19:38
…de (ethereum-optimism#20064)

* chore(deployer): remove OPCMv1 migration, upgrade, and dev feature code

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

* chore: remove remaining v1 fields from ReadImplementationAddresses, clean up stale comments

- Remove opcmDeployer, opcmUpgrader, opcmGameTypeAdder from
  ReadImplementationAddresses.s.sol Output struct and Go binding
  (always zero, v1 inner contracts deleted)
- Keep opcmInteropMigrator (live value from opcmV2.opcmMigrator())
- Remove stale OPCMv1 narration comments and TODO(ethereum-optimism#18612) references

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: opcm with zk and tests

* fix: remove constructor semgrep

* fix: fixing ugrade test

* feat: go params

* fix: update semgrep excludes

* fix: update semgrep excludes

* fix: merge with develop

* fix: cast interface

* fix: comments and tests

* feat: checks in upgrade

* fix: remove

* feat: container in utils

* fix: setup types

* fix: ci needs zk game

* fix: ci

* fix: opcm and utils

* fix: opcm and utils

* fix: ci needs zk game upgrade

* fix: comments and merge with develop

* fix: mrge

* fix: tests bug and merge

* feat: revert short

* feat: revert short

* fix: reordering games

* fix: opcmv2 tests

* fix: remove duplicate

* fix: remove duplicate

* fix: deploy super root

* fix: validator test

* fix: comments

* fix: comments

* fix: merge with dev

* fix: lint

* fix: ci

* fix: comments

* fix: pre pr

* fix: ci go game type

* fix: pre pr

* fix: comment

* fix: comment

* feat: validator and size issue

* fix: tests

* fix: tests

* fix: tests

* fix: expected validator

* fix: stack too deep

* test: increase test coverage

* fix(ci): broken upgarde tests

* fix(ci): broken upgarde tests

* chore: increase OPCM validator version and increase test coverage

* refactor: undo unrelated changes

* fix: regenerate NUT bundle with current forge artifacts

The merge from develop resolved the bundle conflict by keeping
the stale version from this branch. Regenerated via
`just generate-nut-bundle` to match the current compiled artifacts.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: move validZkArgs check to LibGameArgs

* fix: nut bundle

* fix: nut bundle

* fix: nut bundle

* chore: bump OPCMV2 version

---------

Co-authored-by: 0xchin <77933451+0xChin@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…eum-optimism#19997)

* op-acceptance-tests: migrate TestInteropFaultProofs_IntraBlock from op-e2e

Migrate the intra-block fault proof consolidation tests from
op-e2e/actions/interop/proofs_test.go to op-acceptance-tests using the
devstack DSL and supernode infrastructure.

Six of the original seven sub-cases are ported:
- CascadeInvalid / SwapCascadeInvalid (transitive invalidation)
- CyclicDependencyValid (valid cross-chain cycle)
- CyclicDependencyInvalid (both chains invalid)
- SameChainValid / SameChainInvalid (same-chain messaging)

The longDependencyChainValid case and a faithful cyclicDependencyInvalid
(pure exec→exec cycle) are left as TODOs — both require constructing
exec messages that reference other exec messages' ExecutingMessage events,
which the SameTimestampPair API does not yet support.

Closes ethereum-optimism#19010

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

* op-acceptance-tests: add exec-referencing-exec intra-block test cases

Add PrecomputeExecEventMessage and SubmitExecForMessage to the DSL,
enabling construction of exec transactions that reference other exec
transactions' ExecutingMessage events before blocks are built.

Port the remaining two intra-block test cases from op-e2e:
- LongDependencyChainValid: depth-10 exec chain alternating A↔B
- CyclicDependencyInvalid: exec roundtrip A→B→A using fabricated
  pending message, matching the original action test approach

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
)

Fix a handful of prose errors and malformed markdown links found across
the public-docs directory:

Typos / grammar:
- flashblocks-and-gas-usage.mdx: "Genrally" → "Generally"
- blobs.mdx: "introduced in with" → "introduced with" (duplicate word)
- custom-gas-token-guide.mdx: "initent.toml" → "intent.toml"

Broken markdown links (spurious `/> ` prefix breaking all href paths):
- get-started.mdx: fix Supersim and Interop Docs table links
- bridge-crosschain-eth.mdx: fix three Supersim / interop explainer links
- deposit-transactions.mdx: fix interop guides link

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ests (ethereum-optimism#20132)

ethereum-optimism#19960 introduced a contracts-feature-tests workflow gated on
c-contracts_changed, with a required-contracts-ci status that
short-circuits to success via contracts-feature-tests-short when no
contract files changed. But the contracts-bedrock-tests /
-coverage / -upgrade / -l2-fork / -checks-fast job entries were left
in the main workflow too, so every PR that touched any non-docs file
ran the full contracts matrix twice.

Remove the duplicates from the main workflow:
- contracts-bedrock-tests (3 variants: heavy-fuzz-modified, PR, develop)
- contracts-bedrock-coverage
- contracts-bedrock-tests-upgrade (4 variants)
- contracts-bedrock-tests-l2-fork
- contracts-bedrock-checks-fast

Also drop their entries from ci-gate requires and main-skip, since the
required-contracts-ci status now covers the GitHub check.

Kept in main: contracts-bedrock-build, contracts-bedrock-upload,
check-kontrol-build, diff-fetcher-forge-artifacts, l2-chains-sync-check
(these are still depended on by non-contract main-workflow jobs or
produce artifacts consumed outside the contracts feature suite).

As a side-effect this also resolves the duplicate &features_matrix
YAML anchor.
* feat: add ProxyAdminOwnedBase to OptimismMintableERC20Factory

* fix: improve tests for atomic upgrades

* fix: improve tests

* fix: comments
…optimism#20129)

* ci: retry apt-get update on transient mirror sync failures

archive.ubuntu.com periodically returns a stale Packages.gz during
mirror sync, failing apt-get update with "File has unexpected size"
and exit 100. This has been a recurring source of CI flakes across
contracts-bedrock-coverage, rust builds, cannon builds, etc.

Add a shared apt-install CircleCI command that:
- passes -o Acquire::Retries=5 so apt retries individual fetches
- wraps the whole update in a 3-attempt loop with 10s backoff
- standardizes --no-install-recommends plumbing

Replace all apt-get update + install call sites in main.yml and
rust-ci.yml with this command. The one inline call inside a larger
shell script (rust-build-binary) gets the same retry loop inline.

ripgrep install in main.yml does not call apt-get update, so it is
left untouched; docs-ci.yml uses NodeSource's setup_24.x which
manages its own apt config.

* ci: fail fast when apt-get update retries are exhausted

Previously the retry loop would swallow the error and fall through to
apt-get install, which could then succeed against a stale or missing
package index. Exit 1 explicitly after the 3rd failed attempt so the
job fails loudly instead of silently producing a broken environment.
…optimism#20093)

The supernode was hardcoding a 2s L1 HTTP poll interval, causing 2-3x
more L1 RPC requests than equivalent separate op-node deployments since
L1 blocks only arrive every 12 seconds.

- Add --l1.http-poll-interval flag to supernode (default 12s, matching
  op-node) so the shared L1 client's poll interval is configurable
- Add SupernodeOwnedFlags list and warning mechanism: when users set
  op-node flags at the vn.all.* or vn.<id>.* level that are owned by
  the supernode's shared resources, a warning is logged
- Validate that HTTP L1 endpoints have a positive poll interval; zero
  is allowed for websocket connections (disables polling)
- Set poll interval to 100ms in sysgo test configs for fast CI

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tegration (ethereum-optimism#20001)

* feat(op-reth): align interop tx validity window to 86400s

Change TRANSACTION_VALIDITY_WINDOW_SECS from 3600 (1 hour) to 86400
(24 hours) to match op-geth's ingressFilterTxValidityWindow spec.

This constant is used for both the interop deadline set on validated
transactions and the timeout passed to supervisor_checkAccessList.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(op-reth): clarify revalidation window vs ingress timeout

The revalidation window (600s) is intentionally shorter than the ingress
timeout (86400s). Phase 3 evaluation confirmed the block-event-driven
revalidation loop meets all 6 evaluation criteria:
- Fires on every canonical block commit (~2s on OP chains)
- Covers all pooled interop txs with deadlines
- Evicts expired txs and revalidates stale ones
- Failsafe polling (Phase 4) covers the block-gap edge case

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(op-reth): implement block builder failsafe polling

Add background failsafe detection to the interop tx pool:

- Add AtomicBool failsafe_enabled to SupervisorClientInner, shared via
  Arc across all clones
- Add query_failsafe() method that calls admin_getFailsafeEnabled RPC
  and caches the result
- Add is_failsafe_enabled() accessor for the cached state
- Add poll_failsafe() background task that polls every 1s and evicts
  all interop txs from the pool on failsafe transition
- Wire failsafe polling task in node.rs alongside the existing
  maintenance task, using ref+clone pattern to share the supervisor
  client
- Narrow SupervisorClientInner visibility to pub(crate) — no external
  references exist
- Remove Clone derive from SupervisorClientInner (AtomicBool is !Clone,
  inner is always behind Arc)

The ingress filter already rejects new interop txs during failsafe via
CheckAccessList at the source of truth. The polling task provides the
eviction mechanism for already-pooled txs. No changes to the
maintenance loop or payload builder are needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(op-devstack): integrate op-interop-filter for op-reth tx validation

Add in-process op-interop-filter service to devstack for supernode
interop presets:

- Refactor startMixedOpRethNode into build+start pattern to allow
  injecting --rollup.supervisor-http before starting
- Add startSupernodeELWithSupervisorURL for both op-geth and op-reth
- Create InteropFilter wrapper (sysgo/interop_filter.go) running the
  filter service in-process with direct Go struct access
- Add proxy pattern in supernode runtime to break circular dependency
  between EL nodes and filter service
- Add WithInteropFilter() preset option (supernode presets only)
- Add UseInteropFilter to PresetConfig
- Expose InteropFilter on MultiChainRuntime and TwoL2SupernodeInterop
  for direct test access (SetFailsafeEnabled, Ready, FailsafeEnabled)
- Add Ready(), SetFailsafeEnabled(), FailsafeEnabled() pass-through
  methods to filter.Service

No changes to supervisor-based presets or multichain_supervisor_runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(op-acceptance-tests): add interop filter acceptance tests

Add acceptance tests exercising the op-reth + op-interop-filter
topology with the supernode interop preset:

- TestInteropFilter_IngressAcceptsValid: valid interop tx with correct
  cross-chain references passes through the filter
- TestInteropFilter_IngressRejectsInvalid: fabricated CrossL2Inbox
  access list entries are rejected by the filter
- TestInteropFilter_FailsafeBlocksInterop: failsafe toggle blocks new
  interop txs, recovery works after disabling
- TestInteropFilter_NonInteropUnaffected: regular transfers succeed
  regardless of failsafe state
- TestInteropFilter_FailsafeEvictsPooled: failsafe transition evicts
  existing interop txs and rejects new ones

All tests use presets.WithInteropFilter() and direct
InteropFilter.SetFailsafeEnabled() for in-process failsafe control.
Tests are skipped by default (standard interop test pattern).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(op-devstack): defer interop filter readiness wait until after supernode starts

The interop filter's chain ingesters need blocks to backfill, but the
supernode (which drives block production) starts after the filter.
Split the readiness wait out of startInteropFilter and call
WaitForReady after the supernode and batchers are running.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(op-acceptance-tests): keep interop filter tests unskipped

All 5 tests pass with RUST_JIT_BUILD=1 DEVSTACK_L2EL_KIND=op-reth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(op-devstack): address review findings in interop filter integration

Convention fixes:
- Sort imports alphabetically in multichain_supernode_runtime.go
  (rollup with op-node group, tcpproxy after sources)
- Sort imports alphabetically in interop_filter_test.go
  (op-core/predeploys before op-devstack)
- Remove dead execTrigger variable in FailsafeBlocksInterop test
- Revert struct literal alignment to match surrounding style

Test robustness:
- Replace all time.Sleep waits with waitForFailsafeState() helper that
  confirms filter-side state then waits for 2 L2 blocks (guarantees
  op-reth's 1s polling task has had at least 2 poll cycles)
- Make initMsg.Tx.Result.Eval() failures hard errors with
  require.NoError instead of silently skipping the failsafe assertion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(op-reth): use cached failsafe state for ingress rejection and maintenance eviction

Add InvalidCrossTx::FailsafeEnabled variant for fast-path ingress
rejection without RPC round-trip. Add failsafe eviction to the
block-event maintenance loop as belt-and-suspenders with poll_failsafe.

Fix gofmt import ordering in multichain_supernode_runtime.go.

Tighten IngressRejectsInvalid test (10s timeout + explicit rejection
assert). Merge FailsafeBlocksInterop and FailsafeEvictsPooled into
FailsafeLifecycle. Extend NonInteropUnaffected to test both chains.

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

* refactor(op-reth): unify interop tx identification under is_interop_tx

Move is_interop_tx to interop.rs as the single pub(crate) helper for
identifying interop transactions by access list. Use it in maintain and
poll_failsafe eviction paths instead of interop_deadline().is_some(),
ensuring all codepaths use the same structural check.

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

* fix(op-acceptance-tests): use single-attempt submit in IngressRejectsInvalid

The test used bob.Plan() which includes WithRetrySubmission (5 retries,
exponential backoff). Under CI load, retrying the rejected tx exhausted
the 10s context timeout, causing the test to see DeadlineExceeded instead
of the filter's explicit rejection error.

Fix: override with WithTransactionSubmitter (no retries) and evaluate
tx.Submitted instead of tx.Included, so the filter rejection propagates
on the first attempt.

Also unifies interop tx identification under is_interop_tx in interop.rs
so all codepaths (ingress, maintenance, failsafe eviction, reorg) use
the same structural access-list check.

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

* fix(op-acceptance-tests): address review feedback on interop filter tests

- Remove TestInteropFilter_IngressAcceptsValid (redundant with existing
  message tests; enable WithInteropFilter() on TestInteropHappyTx instead)
- Remove waitForFailsafeState helper and its 2-block wait heuristic
- Relax failsafe error assertion to accept any rejection (both op-reth
  fast-path and filter HTTP path correctly reject during failsafe)
- Replace retry.Do0 recovery loop with single init/exec attempt
- Simplify failsafe propagation to single WaitForBlock() calls

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

* fix(op-acceptance-tests): accept interop rejection errors from any EL backend

Add interopTxRejectedError helper that matches rejection messages from
op-geth ("transaction filtered out"), op-reth ("interop failsafe is
active"), and op-interop-filter ("failed to parse access entry",
"failsafe is enabled"). Use it in both IngressRejectsInvalid and
FailsafeLifecycle tests so they pass regardless of which component
rejects the transaction.

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

* fix(op-reth): reduce TRANSACTION_VALIDITY_WINDOW_SECS to 2 hours

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

* refactor(op-reth): rename to CHECK_ACCESS_LIST_TIMEOUT_SECS and unify timeout

Rename TRANSACTION_VALIDITY_WINDOW_SECS to CHECK_ACCESS_LIST_TIMEOUT_SECS
and remove the separate TRANSACTION_VALIDITY_WINDOW constant in maintain.rs,
sharing a single 7200s timeout for both ingress validation and revalidation.

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

* fix(op-acceptance-tests): use test context instead of context.Background

Replace context.Background() with gt.Context() / t.Ctx() so that
test contexts are properly cancelled on test failure or timeout.

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

* fix(op-acceptance-tests): document untested txpool eviction behavior

Add a comment explaining why txpool eviction on failsafe activation is
not covered: StopSequencer only prevents new payload requests but an
in-flight engine API payload can still land, creating a race between
block inclusion and failsafe eviction.

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

* chore: rustfmt

---------

Co-authored-by: wwared <541936+wwared@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…mism#20126)

* ci: bump factory workflow to node24-compatible version

* ci: fix factory attest step (bump to corrected SHA)
…thereum-optimism#20121)

* chore(ci): register ci-base-clang image for factory builds

Add ci-base-clang to docker-bake.hcl and docker-images.json so the
factory workflow builds and publishes the image to Artifact Registry.
The image provides cimg/base with clang pre-installed for Rust bindgen
jobs, eliminating the ~9 minute apt-get install on every CI run.

Depends on ethereum-optimism#20117 which adds the Dockerfile.
Closes ethereum-optimism#20119

* chore(ci): add ci-base-clang image and register for factory builds

Add a CI base image that pre-bakes clang, llvm-dev, and libclang-dev
onto cimg/base:2026.03 for Rust bindgen jobs. Register it in
docker-bake.hcl and docker-images.json so the factory workflow builds
and publishes to Artifact Registry with provenance attestation.

Eliminates the ~9 minute apt-get install on every Rust CI run.

Based on the Dockerfile from ethereum-optimism#20117.
Closes ethereum-optimism#20119

* chore(ci): apply security patches in ci-base-clang image

Run apt-get upgrade before installing packages to pick up security
patches that may not yet be in the upstream cimg/base image.
* feat: cleanup contracts

* fix: remove fee splitter as predeploy

* feat: remove references from l2cm

* feat: remove genesis and deploy script paths

* fix: tests cleanup

* fix: linting

* fix: cleanup go packages

* fix: deploy configs

* fix: further op packages fixes

* fix: go tests

* fix: just pr

* fix: missed refferences in tests

* fix: tests and lint

* fix: conflicts

* fix: missed cleanup

* chore: keep audits

* fix: conflicts

* fix: conflicts

* fix: ci

* fix: conflicts

* fix: conflicts
ethereum-optimism#20140)

ethereum-optimism#19685 added zkDisputeGameImpl to VerifyOPCM's implementations map but
forgot the corresponding skip path for when DEV_FEATURE__ZK_DISPUTE_GAME
is off. OPCM returns address(0) for zkDisputeGameImpl in that case, so
VerifyOPCM treats the 0-byte deployed code vs 13.5KB expected artifact
as a verification failure.

This broke contracts-bedrock-tests-develop on every matrix cell that
doesn't set ZK_DISPUTE_GAME (11 of 12), but wasn't caught at merge time
because the -develop job runs post-merge only and isn't in
required-contracts-ci.

Three tests were failing on develop for the same root cause:
- test_run_succeeds — VerifyOPCM_Failed, ZK ref mismatches artifact
- test_run_implementationDifferentInsideImmutable_succeeds — same
- test_run_implementationDifferentOutsideImmutable_reverts — panic 0x11
  (vm.randomUint(0, implCode.length - 1) underflows when ZK ref's code
  is empty)

Fix mirrors the existing SuperDisputeGame skip path:
- VerifyOPCM.s.sol: skip-or-fail branch inside _verifyOpcmContractRef
  plus _isZKDisputeGameEnabled / _isZKDisputeGameImplementation helpers.
- VerifyOPCM.t.sol: skip the ZK ref in the byte-scramble loops when
  the feature is off so implCode.length == 0 doesn't underflow, and
  unconditionally skip ZK in test_runSingle_succeeds (mirrors the
  existing super-games skip with TODO).
…m-optimism#20142)

SchemaResolver (src/vendor/eas/resolver/SchemaResolver.sol) is an
abstract contract with zero concrete subclasses anywhere in the
monorepo — verified by direct "is SchemaResolver" grep and
import-graph analysis.

The companion interface ISchemaResolver is still used by EAS.sol,
SchemaRegistry.sol, and ISchemaRegistry.sol — that stays.

Net: -165 LoC.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…imized (ethereum-optimism#20143)

* fix(contracts): skip ZKDisputeGame in VerifyOPCM when feature disabled

ethereum-optimism#19685 added zkDisputeGameImpl to VerifyOPCM's implementations map but
forgot the corresponding skip path for when DEV_FEATURE__ZK_DISPUTE_GAME
is off. OPCM returns address(0) for zkDisputeGameImpl in that case, so
VerifyOPCM treats the 0-byte deployed code vs 13.5KB expected artifact
as a verification failure.

This broke contracts-bedrock-tests-develop on every matrix cell that
doesn't set ZK_DISPUTE_GAME (11 of 12), but wasn't caught at merge time
because the -develop job runs post-merge only and isn't in
required-contracts-ci.

Three tests were failing on develop for the same root cause:
- test_run_succeeds — VerifyOPCM_Failed, ZK ref mismatches artifact
- test_run_implementationDifferentInsideImmutable_succeeds — same
- test_run_implementationDifferentOutsideImmutable_reverts — panic 0x11
  (vm.randomUint(0, implCode.length - 1) underflows when ZK ref's code
  is empty)

Fix mirrors the existing SuperDisputeGame skip path:
- VerifyOPCM.s.sol: skip-or-fail branch inside _verifyOpcmContractRef
  plus _isZKDisputeGameEnabled / _isZKDisputeGameImplementation helpers.
- VerifyOPCM.t.sol: skip the ZK ref in the byte-scramble loops when
  the feature is off so implCode.length == 0 doesn't underflow, and
  unconditionally skip ZK in test_runSingle_succeeds (mirrors the
  existing super-games skip with TODO).

* test(contracts): skip VerifyOPCM_Run_Test on coverage only, not unoptimized

These three tests compare a locally-compiled deployed address to a
locally-compiled artifact on disk. Both sides come from the same build,
so the comparison holds under any Foundry profile. Coverage mode does
break it (the coverage run instruments the deployed code but not the
on-disk artifact), but an unoptimized profile like liteci does not.

ethereum-optimism#19332 mechanically renamed every skipIfCoverage() → skipIfUnoptimized()
in this file while introducing the liteci profile. That over-broadened
the skip: on PRs we now run liteci via contracts-bedrock-tests, and
VerifyOPCM_Run_Test short-circuits there, so the script's correctness
is only exercised post-merge by contracts-bedrock-tests-develop. That
is how the ZK-dispute-game skip miss from ethereum-optimism#19685 slipped past merge
and landed red on develop.

Reintroduce skipIfCoverage() as a distinct helper and apply it to the
three tests whose only real compiler-output dependency is coverage
instrumentation:
- test_run_succeeds
- test_run_implementationDifferentInsideImmutable_succeeds
- test_run_implementationDifferentOutsideImmutable_reverts

The other skipIfUnoptimized() uses in this file are left alone — some
of them genuinely depend on optimized bytecode (e.g. immutable-variable
tests, preimage-oracle checks with fixed selectors) and are worth
auditing separately if/when someone wants to widen the PR coverage.

Stacked on fix/verifyopcm-zk-dispute-skip so the PR-level liteci run
doesn't go red on non-ZK matrix cells; land that one first.
…#20141)

* chore(contracts): remove 7 unreferenced interfaces

Found via import-graph analysis — all 7 have zero importers anywhere in
the monorepo:

- IL1CrossDomainMessengerV160.sol — also already broken (imports the
  non-existent IOptimismPortal.sol; only IOptimismPortal2.sol exists)
- IL1StandardBridgeV160.sol — archival v1.6.0 sibling
- IHasSuperchainConfig.sol — only trace was an entry in the interfaces
  checker's own exclusion list
- IOPContractsManagerUtilsCaller.sol — vestigial stub for an abstract
  contract; add the concrete OPCM sub-contract to excludeSourceContracts
  alongside the existing OPContractsManagerStandardValidator
- IERC7802.sol — the docs tutorials that reference IERC7802 import it
  from lib/interop-lib/, not from here
- IMintableAndBurnableERC20.sol — zero importers
- IERC20Solady.sol — zero importers

Companion edit: drop the now-dead "IHasSuperchainConfig" line from the
excludeContracts list in scripts/checks/interfaces/main.go.

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

* chore(contracts): update interface checker exclusion list

Companion to the prior commit — interfaces/main.go was modified but not
staged before the first commit landed.

- Drop "IHasSuperchainConfig" from excludeContracts (the interface file
  it referenced was deleted)
- Add "OPContractsManagerUtilsCaller" to excludeSourceContracts next to
  the sibling OPContractsManagerStandardValidator (abstract OPCM
  sub-contract with no interface, same pattern)

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…imism#19976)

* fix(op-devstack): stabilize flaky TestFlashblocksStream

Fix two compounding bugs in the op-node error callback:

1. Goroutine safety: Replace Require().NoError (calls FailNow/Goexit)
   with Errorf in l2_cl_opnode.go and l2_cl_supernode.go. The errFn
   callback runs from a background goroutine, so FailNow only exits
   that goroutine — leaving the test hanging until timeout.

2. Wrong error reported: Pass errCause (the original critical error)
   to errFn instead of the stop error. The stop context is
   pre-cancelled, so Stop returns "context canceled" which obscures
   the real failure.

Refs: ethereum-optimism#19883

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

* fix(flashblocks): wait for builder P2P sync during rapid block driving

When driveViaTestSequencer builds blocks rapidly, the sequencer EL
produces blocks faster than P2P can propagate them to the builder EL
(op-rbuilder). Under CI load, the builder receives an FCU referencing
a parent block it hasn't synced yet, causing "missing parent header" →
InvalidPayloadAttributes → CriticalError → test hang.

Poll the builder's L2EthClient after each driven block to ensure it
has synced the block before driving the next one. This also benefits
TestFlashblocksTransfer which uses the same helper.

Refs: ethereum-optimism#19883, ethereum-optimism#19934

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

---------

Co-authored-by: wwared <541936+wwared@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…test (ethereum-optimism#20071)

Assert that the P256VERIFY precompile gas cost increases from 3,450
(RIP-7212) to 6,900 (EIP-7951) at Karst activation. Also update the
karst() precompile set in op-revm to swap secp256r1::P256VERIFY for
secp256r1::P256VERIFY_OSAKA.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…thereum-optimism#20062)

* feat(kona-hardforks): add NUT bundle types and deposit tx conversion

Add types and conversion logic for JSON-defined Network Upgrade
Transaction bundles, enabling future forks to define NUTs as data
rather than hardcoded Rust structs.

* refactor(op-alloy): move NUT bundle types to op-alloy-consensus

Address review feedback: NUT types only depend on alloy, so they
belong in op-alloy-consensus where any Rust tooling can use them
without pulling in kona. Also uses thiserror, improves test fixtures,
and adds explicit hash/encoding regression assertions.

* refactor(kona-hardforks): drop unused NUT type re-exports

The re-exports of NetworkUpgradeTransaction/NutBundle/NutBundleError
from op_alloy_consensus aren't consumed by kona-hardforks or any other
crate yet. Add them back when the first downstream user appears.

Made-with: Cursor

---------

Co-authored-by: maurelian <maurelian@protonmail.com>
ethereum-optimism#20069)

Assert that OP Stack chains can build blocks exceeding the EIP-7934
10 MiB RLP block size limit. Submits many transactions with large
calldata under a 200M gas limit and verifies a single block exceeds
10 MiB in total transaction data.

Also adds WithGasLimit to L2Configurator and WithL2GasLimit deployer
option to support custom L2 block gas limits in tests.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…L2 test (ethereum-optimism#20112)

* test(op-acceptance-tests): add EIP-7934 block size limit disabled test

Assert that OP Stack chains can build blocks exceeding the EIP-7934
10 MiB RLP block size limit. Submits many transactions with large
calldata under a 200M gas limit and verifies a single block exceeds
10 MiB in total transaction data.

Also adds WithGasLimit to L2Configurator and WithL2GasLimit deployer
option to support custom L2 block gas limits in tests.

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

* refactor(op-acceptance-tests): deduplicate Karst fork setup in Osaka L2 tests

Extract shared setup code into setupKarstForkTest helper to reduce
repetition across four tests that all activate Karst at block offset 3.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ckTimes tests as flaky (ethereum-optimism#20159)

Removes MarkFlaky("ethereum-optimism#19828") from
TestPreinteropFaultProofs_VariedBlockTimes and its _FasterChainB
variant. Held as draft until a local 15x sweep confirms stability.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ethereum-optimism#20135)

* ci: pin ci-base-clang image for Rust jobs and skip runtime apt-install

Adds a new pipeline parameter `rust_base_image` pinned by digest to the
factory-built ci-base-clang image (cimg/base + clang/llvm-dev/libclang-dev).
Switches Rust job executors in main.yml, rust-ci.yml, and rust-e2e.yml to
use this image, and replaces the runtime `apt-install clang` step with a
fast `command -v clang` verification.

Provenance of the pinned digest is verified by a follow-up GitHub Actions
workflow (sub-issue ethereum-optimism#20122).

Closes ethereum-optimism#20120

* ci: keep apt-install fallback for machine-executor jobs

The previous commit replaced the Install clang step with a hard verify,
but kona-cargo-lint, kona-build-fpvm, and kona-host-client-offline run on
`machine:` executors (raw Ubuntu VMs) where clang isn't pre-baked. Add a
guarded fallback: if clang is already present (docker jobs using
c-rust_base_image), skip; otherwise apt-install as before.

* ci: add apt-get retry loop in clang fallback for machine VMs

The previous fallback used a bare `apt-get update` which timed out on
machine-executor jobs against transient Ubuntu mirror sync failures.
Use the same retry loop as the apt-install orb (3 attempts, 10s sleep).

* ci: fail fast if clang missing in rust-prepare

Remove apt fallback in rust-prepare (review feedback). Install clang on
machine executors via apt-install before rust-prepare.

* ci: quote rust-prepare command for valid YAML

Unquoted ERROR: in the shell string was parsed as a YAML mapping key and broke CircleCI config generation.

* ci: quote rust-ci rust-prepare command for valid YAML

Same unquoted ERROR: issue as main.yml; path-filtering merges rust-ci.yml and the setup job failed YAML validation.
…ust test (ethereum-optimism#20161)

The `just test` recipe hardcoded `-count=1 -timeout 30m`, which blocked
callers from requesting e.g. repeated runs with `-count=10` for flake
triage. Now the defaults are applied only when the flag is absent from
the forwarded args, so callers can override either.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hereum-optimism#20171)

Adds a workflow that reads the pinned ci-base-clang digest from
.circleci/config.yml and runs `gh attestation verify --bundle-from-oci`
against it, enforcing the attestation was signed by the factory reusable
workflow for `ethereum-optimism/optimism` on `refs/heads/develop`. The
workflow runs on push to develop and on PRs that touch the pin or the
workflow itself, so a digest swap to any unattested or off-branch image
blocks the merge.

Also repins `rust_base_image` to the digest built from develop
(sha256:00f641689576d7393d83f6fd49fe1592006148305999f1ba2fc4f1f9d2e8a342)
so the new check passes on merge.

Closes ethereum-optimism#20122
…ptimism#20170)

The branch build `prep` job was failing to install `just` via the
`jdx/mise-action`. Newer mise releases (>=2026.4.18) changed the `ubi`
backend to bypass the `mise-versions.jdx.dev` cache and query GitHub
directly, which broke tag resolution for projects like casey/just that
use a non-`v` release tag prefix. The resulting HTTP 404 against
`https://api.github.com/repos/casey/just/releases/tags/v1.46.0` caused
the prep job to fail.

Pin the mise version in the composite action to 2026.2.2 to match the
mise version used by the CircleCI `install-mise` orb command
(`ethereum-optimism/circleci-utils`).

Refs failing run:
https://github.com/ethereum-optimism/optimism/actions/runs/24638787954
…-optimism#19973)

* feat(op-node): offset_derived for EL-sync safe/finalized retraction

Add duration-based offset_derived (rollup BlockTime -> block steps) so EL-sync
completion sets safe, finalized, and local-safe to an ancestor of the unsafe
tip; unsafe stays at tip. Uses a single L2BlockRefByNumber call instead of
walking N parent hashes. Default --syncmode.offset-derived is 168h (7d).

Plumb through sync config, CLI, EngineController FCU, and FindL2Heads recovery.
sync.L2Chain now includes L2BlockRefByNumber. Tests use table-driven subtests.

Made-with: Cursor

* test(op-e2e): add e2e test for OffsetELSafe after EL sync

Refactor PrepareELSyncedNode to accept expected safe/finalized block
numbers, and add TestELSyncOffsetELSafe demonstrating that safe and
finalized heads retract from the tip when OffsetELSafe is configured.

Made-with: Cursor

* test(acceptance): add OffsetELSafe acceptance test for EL sync

Plumb OffsetELSafe through L2CLConfig into the devstack sync config,
and add TestELSyncSafeRetractedByOffset which verifies that safe and
finalized heads are retracted from the unsafe tip after EL sync.

Made-with: Cursor

* test(acceptance): add expected retraction check to OffsetELSafe test

Log a warning when observed retraction doesn't match the expected
5-block retraction (10s offset / 2s block time).

Made-with: Cursor

* fix(op-node): address OffsetELSafe self-review feedback

- Default to 0 (full nodes can't progress safe derivation from offset)
- DurationToBlocks uses ceiling division so the retraction always
  covers the full requested duration (e.g. 15s / 4s blockTime = 4)
- Extract OffsetBlockNum helper to deduplicate clamped-offset logic
  between start.go and engine_controller.go
- Rename `span` to `maxRetract` for clarity

Made-with: Cursor

* lint

* fix(op-node): align EL-sync offset test with ceiling division

The test expected floor(offset/BlockTime) retraction but
DurationToBlocks uses ceiling division. With offset=5s and
BlockTime=2, ceil(5/2)=3 retracts to genesis (block 0), not
block 1. Update expected values and test name accordingly.

Made-with: Cursor

* fix(op-node): never retract finalized/safe behind prior values after EL sync

With SupportsPostFinalizationELSync, EL sync can start even when there
is an existing finalized head (Erigon/Reth). The offset retraction must
not move finalized or safe backwards, so clamp each against its prior
value before applying the forkchoice update.

Made-with: Cursor

* fix(op-node): reject OffsetELSafe when CL sync is enabled

OffsetELSafe only applies to EL sync completion. With CL sync it would
needlessly retract the safe head during FindL2Heads recovery paths.
Fail at startup instead of silently misbehaving.

Made-with: Cursor

* fix(acceptance): make OffsetELSafe test deterministic

Stop the batcher before advancing the sequencer so new blocks are
unsafe-only. This creates a permanent gap between safe and unsafe
that derivation can never close, eliminating timing-dependent flakes.

Made-with: Cursor

* feat(op-supernode): interop log backfill depth and validation gating

Add --interop.log-backfill-depth to pre-ingest initiating logs from
T_lo = max(activation, tip - depth) through local safe. ChainContainer gains
TimestampToBlockNumber. Backfill and T_lo math live in log_backfill.go (run loop,
ceiling snapshot, skip-verify gating). SkipPersistFrontierLogs on advance when
logs were backfilled. Config validation and table-driven tests;
log_backfill_test.go covers bounds + integration.

Made-with: Cursor

* refactor(op-supernode): replace skip-verification with activation advancement

Instead of maintaining a backfillCeilingTimestamp and skip-verification
machinery, advance activationTimestamp past the backfilled range after
log backfill completes. VerifiedAtTimestamp already treats timestamps
before activation as verified, so this naturally does the right thing
without shouldSkipVerification, SkipPersistFrontierLogs, or maxL1Inclusion.

Made-with: Cursor

* fix: remove stale kona and reth submodule entries

These gitlink entries were left behind when the rust workspace
unification (ethereum-optimism#19034) moved kona to rust/kona/ and removed reth.
The dangling references break CI checkout because .gitmodules has
no matching URLs.

Made-with: Cursor

* PR Comments

- Retry log backfill when virtual nodes aren't ready instead of failing
- Remove deterministic chain ID sorting in runLogBackfill
- Delete stale signer_grace_period_Feature.md
- Add syncStatusOverride to mock for dynamic SyncStatus control in tests

Made-with: Cursor

* PR Comments 2

Made-with: Cursor

* Acceptance Testing

Made-with: Cursor

* ensure log backfill in tests

Add a test-only primitive to hot-restart just the interop activity (optionally
wiping its logs DBs and pre-injecting synthetic backfill failures) while the
rest of the supernode keeps running, and use it to drive deterministic happy-
path and retry acceptance tests that assert backfill actually populates the
logs DB against a fully synced virtual-node cluster.

Made-with: Cursor

* PR review: fixes and thin-wrapper collapse

Addresses reviewer feedback on the interop log-backfill PR and cleans up
the test-control surface that grew during the review cycle.

Review-response fixes:
- Add TestLogBackfill_AsymmetricMultiChain covering the skipped-chain /
  runtime-activation edge case, backed by a new timestampToBlockNumberOverride
  hook on mockChainContainer.
- Protect simpleChainContainer.verifiers with an RWMutex so ReplaceVerifier
  (used by RestartInteropActivity) cannot race with VerifierCurrentL1s /
  VerifiedAt readers.
- Protect Supernode.activities with an RWMutex so RestartInteropActivity's
  slice swap cannot race with onChainReset / InteropActivity readers.
- Move the DataDir precheck in RestartInteropActivity ahead of old.Stop()
  so a misconfigured call fails before tearing down the old activity.
- Demote the per-attempt "log backfill: computed lower bound" log line
  from Info to Debug; the end-of-backfill summary remains the user-visible
  signal.

Thin-wrapper collapse (~180 lines net reduction):
- Replace eight pass-through methods on Supernode (PauseInteropActivity,
  ResumeInteropActivity, InteropBackfillAttempts, InteropBackfillCompleted,
  InteropActivationTimestamp, InteropRuntimeActivationTimestamp,
  InteropFirstSealedBlock, InteropLatestSealedBlock) with a single
  InteropActivity() accessor. RestartInteropActivity stays - it has real
  logic.
- Do the same on sysgo.SuperNode, shrinking the test-control surface to
  InteropActivity() + RestartInteropActivity.
- Shrink stack.InteropTestControl from 11 methods to 2.
- Introduce a dsl.Supernode.interopActivity() helper so each DSL method
  does a single nil-guarded dereference and then calls the real interop
  method directly (PauseAt, BackfillAttempts, FirstSealedBlock, ...).

Made-with: Cursor

* fix(op-supernode): fail backfill when a chain is behind T_lo

runLogBackfill previously computed minLocalSafeTime over every chain,
including ones whose local-safe was behind T_lo in block space and
therefore got silently skipped by the "startNum > localSafeNum" branch.
After backfill, runtimeActivationTimestamp = minLocalSafeTime + 1; the
main loop then advanced past the unbackfilled range on the skipped
chain, leaving its [T_lo, minLocalSafeTime] logs unsealed. Any future
executing message that referenced an initiating message in that range
would be unverifiable.

By the supernode's own invariants (minCrossSafeTime <= every chain's
localSafeTime, and T_lo <= minCrossSafeTime) the skip branch is
unreachable in steady state; hitting it means SyncStatus and
TimestampToBlockNumber disagreed (chain still warming up, derivation
indexing lag, etc.). The right response is to fail the round and let
the retry loop re-query once the inconsistency resolves, rather than
silently lose logs.

Replace the skip branch with a fail-fast error that names the chain
and both block numbers. Fold localSafeTime into minLocalSafeTime only
after the consistency check, so the runtime-activation advancement
always reflects the set of chains whose logs DB is now populated up
to T_lo.

Rewrite TestLogBackfill_AsymmetricMultiChain as a healthy three-chain
case with asymmetric cross-safe and local-safe positions, and add
TestLogBackfill_FailsWhenChainBehindTLo to pin the new safety property.

Made-with: Cursor

* refactor(op-supernode): export backfill-lower-bound sentinel, split multichain tests

Follow-up to the fail-fast fix: callers now have a sentinel
(ErrChainBehindBackfillLowerBound) to match on, and the error message
includes localSafeTime and minCrossSafeTime so the operator can see
exactly which SyncStatus field is inconsistent.

Split TestLogBackfill_AsymmetricMultiChain back into a clean healthy
three-chain case (all chains backfill, runtimeActivation pins to the
smallest localSafe+1) and a dedicated TestLogBackfill_FailsWhenChainBehindLowerBound
that uses the timestampToBlockNumberOverride hook to force the
inconsistent-SyncStatus path and asserts the error wraps the sentinel
and runtimeActivation stays at activationTimestamp on failure.

Made-with: Cursor

* refactor(op-acceptance-tests): inline AssertBackfillCovered wrapper

AssertBackfillCovered in backfillutil was a two-line shim around
Supernode.AssertBackfillCovers. Inline the call at both test sites so
the DSL assertion is reached directly, and drop the helper. Matches
the thin-wrapper cleanup already done across the DSL/stack/sysgo layers
in this PR.

Made-with: Cursor

* refactor(op-supernode): use op-service/clock.SleepCtx for backoff waits

Swap the three wall-clock calls in Interop.Start for
clock.SystemClock.SleepCtx. Two consequences beyond the import swap:

- The backfill retry loop's `select { <-ctx.Done; <-time.After }` becomes
  a single SleepCtx call with identical semantics.
- The main loop's `time.Sleep(errorBackoffPeriod)` and
  `time.Sleep(backoffPeriod)` previously ignored ctx mid-sleep, so a
  cancellation would stall the goroutine for up to 2s. SleepCtx returns
  ctx.Err() immediately on cancel, and Start now propagates that error
  so Stop is observed without waiting out a backoff.

The `time` import stays for the `time.Second` / `time.Duration`
constants on the backoff vars, which clock also uses.

Made-with: Cursor

* fix(op-supernode): allow rollup-derived activation to satisfy backfill pairing

config.Check() previously rejected --interop.log-backfill-depth=...
unless --interop.activation-timestamp was also set on the CLI, but
activation can equally come from each chain's rollup config
(vnCfg.Rollup.InteropTime). CLIConfig can't see rollup configs, so
the old pairing check ran in the wrong layer and blocked a valid
configuration.

Move the pairing check to supernode.New, after
resolveInteropActivationTimestamp has picked a source (CLI override
wins, otherwise consistent rollup configs). Depth+rollup now works;
depth with no activation source anywhere still fails, with an error
that names both remediation paths.

Extract the check into checkLogBackfillRequiresInteropActivation so
it's unit-testable without standing up a full supernode, and update
the config-level test to only assert Check()'s actual contract
(well-formed CLI inputs, independent of rollup).

Made-with: Cursor

* lint and move util to testutil

* lint

* address karlfloersch PR comments

- Remove backfillFailuresToInject test-only injection and the synthetic
  failure path in runLogBackfill; retry behavior is already covered by
  the TestLogBackfill_RetriesWhenVirtualNodesNotReady /
  TestLogBackfill_RetriesStopOnContextCancel unit tests, so keeping the
  injection check in the production function was unnecessary noise.
- Drop the preInjectBackfillFailures parameter from RestartInteropActivity
  across supernode / sysgo / stack / dsl layers.
- Delete the now-redundant backfill/retry acceptance test; happy path
  still exercises restart + wipe + backfill end-to-end.
- Revert unrelated findMonorepoRoot bump (10 -> 6).
- Restore "/restart" in the SuperNode.snCfg/vnCfgs comment; Stop+Start is
  a restart of the whole supernode and uses those stored configs.

Made-with: Cursor

* fix(op-supernode): accept pre-activation pairing anchor in backfill

When interop activation does not land on a (genesis + k*blockTime)
boundary, rollup.TargetBlockNumber(activation) floors to the last block
whose Time() is strictly before activation. That block *is* the chain
state as of the fork — the correct pairing anchor for the first
post-activation block — but the previous check in verifyCanAddTimestamp
required ts >= activationTimestamp, so backfill's first seal returned
ErrPreviousTimestampNotSealed and the retry loop in Interop.Start spun
forever with a misleading "virtual nodes may not be ready" warning.

Relax the empty-DB backfill branch to ts+blockTime > activationTimestamp:
the first seal may be up to one blockTime before activation, and strictly
earlier ts still errors. The main-loop path (isBackfill=false) is
untouched.

AssertBackfillCovers' first-timestamp invariant is widened in lockstep.

Add TestLogBackfill_MisalignedActivation, which wires blockTime=3,
activation=1000, and a floor-rounding TimestampToBlockNumber so the
mock exercises the exact misalignment path. The mock grows
blockTimeOverride and blockInfoTimeFn hooks; FetchReceipts now derives
parentHash from blockID.Number-1 (previously from ts-1, which coincided
only when blockNum==ts).

Made-with: Cursor

* fix(op-devstack): raise findMonorepoRoot search depth to 10

The backfill acceptance tests live at
op-acceptance-tests/tests/supernode/interop/backfill/happy (6 levels
under the monorepo root), which exceeds the 6-iteration cap and trips
"failed to find monorepo root using packages/contracts-bedrock". The
earlier 6->10 change was rolled back per review feedback, but the
reviewer and I missed that this specific test depth required it.
Reinstate 10 with a comment naming why.

Made-with: Cursor
…-optimism#20190)

Replaced multiple `ci-gate-skip` jobs with a single `ci-gate` job that always succeeds and includes a context for the CircleCI API token. This simplifies the workflow and ensures consistent execution of the CI pipeline.
… number (ethereum-optimism#20162)

The is_interop_active() function expects a block timestamp but was
receiving a block number. Since block numbers are always smaller than
timestamps, interop was never detected as active in the proof driver,
causing EndOfSource errors to be silently swallowed instead of
propagated to the caller.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
smartcontracts and others added 26 commits April 25, 2026 14:36
* chore(contracts-bedrock): fix contract typos

* chore(contracts-bedrock): clean up contract comments

* chore(contracts-bedrock): update contract version checks
… update in CL-sync mode (ethereum-optimism#20310)

When an execution client (e.g. op-reth) is restarted, it may lose its in-memory
canonical state and return SYNCING to forkchoice updates. Without this check,
the SYNCING response gets recorded as the last-accepted forkchoice via
`e.lastForkchoice = fc`, and the subsequent `if fc == e.lastForkchoice { return nil }`
guard suppresses further FCU retries — op-node stalls until restarted.

This fix uses the existing checkForkchoiceUpdatedStatus method to validate the FCU
response before recording lastForkchoice. In CL-sync mode, SYNCING triggers a
ResetError, so FindL2Heads re-discovers the EL's actual chain state. In EL-sync
mode, SYNCING remains accepted (existing behavior preserved).

This resolves the issue where op-reth reboots require an op-node restart to recover.

Co-authored-by: Samuel Stokes <sam.adam.stokes@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…um-optimism#20352)

* fix(op-node/engine-controller): management of e.localSafeHead in
initializeUnknowns.

Fix initializUnknowns to set localSafeHead to a nonzero ref rather than
to e.SafeL2Head() which would erroneously just set it to zero again.

Rename SetSafeHead to SetDeprecatedSafeHead
Mark the method as deprecated for supervisor-only pathways.

* Fix typo in SetDeprecatedSafeHead doc comment

* Add regression test for localSafeHead initialization bug

Tests that initializeUnknowns properly sets localSafeHead when
restarting op-node. The bug caused safe head to never advance after
restart because SetSafeHead was setting deprecatedSafeHead instead
of localSafeHead.
…optimism#20288)

Mirrors kona PR op-rs/kona#3158. When set (non-zero), bypasses the
beacon /eth/v1/config/spec fetch and uses the given value as
SECONDS_PER_SLOT. Useful for devnets where the beacon spec endpoint
is unavailable (e.g. anvil).
…thereum-optimism#20347)

* fix(op-fetcher): guard against zero mipsImpl before calling oracle()

Guard mipsImpl != address(0) before calling oracle() so the script handles
disabled or zeroed-out permissioned game implementations gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* use LibGameArgs in _processFaultProofs primary path

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…imism#20303)

* feat(kona-interop): add cycle detection to MessageGraph

MessageGraph validated executing messages independently via
check_single_dependency, which cannot detect circular dependencies
between same-timestamp cross-chain messages. op-supernode rejects
these cycles using Kahn's topological sort; kona accepted them.

Add cycle detection to MessageGraph::resolve() that runs before
per-message validation. The implementation matches op-supernode's
buildCycleGraph/checkCycle semantics: intra-chain sequential edges,
cross-chain edges via executingMessageBefore, and Kahn's algorithm
to identify cycle participants while sparing bystander chains.

- Add CyclicDependency error variant to MessageGraphError
- Add executing_log_index to EnrichedExecutingMessage for graph construction
- Handle CyclicDependency in the consolidation retry loop
- Fix test_derive_and_resolve_simple_graph_with_cycles to assert rejection
- Add tests for triangle cycles, one-way deps, and bystander chains

Closes ethereum-optimism#20196

* improve tests

* fmt/clippy

* fix(kona-interop): filter historical-timestamp EMs from cycle graph

Address review feedback on ethereum-optimism#20303 (ajsutton). Layer a second filter on
detect_cycles so an executing message is admitted into the same-timestamp
cycle graph only when its *executing block* timestamp **and** its
referenced *initiating message* timestamp both equal the target timestamp.

Previously only the executing-block timestamp was checked, matching
op-supernode's primary filter in verifyCycleMessages but missing the
secondary filter in buildCycleGraph (em.Timestamp == ts, sourced from
MessageIdentifier.Timestamp). Without the secondary filter, EMs that
reference historical initiating messages were drawn into the current-ts
graph and could fabricate spurious cross-chain edges through
executingMessageBefore, producing false-positive cycles.

Fold in the regression test from ethereum-optimism#20305 and add a direct unit test for
the new filter condition. Generalize the make_em helper so tests can
specify the initiating-message timestamp independently of the executing
block timestamp. Tighten the MessageGraph doc comment: blocks in the
graph may be at different timestamps, one per chain, and expand the
CyclicDependency variant doc to record that each error represents a
single cycle at a single timestamp; multi-timestamp cycles surface
iteratively through the consolidation retry loop.

* test(kona-interop): add op-supernode parity cycle-detection cases (ethereum-optimism#20354)

Mirrors four boundary cases from op-supernode's cycle_test.go that did not
have Kona equivalents:

- triangle with missing leg (cycle_test.go:454-462)
- diamond pattern (cycle_test.go:268-287, from TestCheckCycle)
- bystander chain refs absent chain (cycle_test.go:418-426)
- multi-hop one-way dependency (cycle_test.go:428-434)

All added at the existing `test_detect_cycles_*` layer using the existing
`make_em` helper — no new test scaffolding. Diamond layout co-locates the
sink EM on the same chain as one of its parents so the intra-chain edge
provides the second incoming dependency.

Co-authored-by: wwared <541936+wwared@users.noreply.github.com>

---------

Co-authored-by: wwared <wwared@users.noreply.github.com>
Co-authored-by: wwared <541936+wwared@users.noreply.github.com>
* U19 Notice page update

Updates U19 notice page with additional details for improved clarity

* Clarify update instructions for op-reth version

* Apply suggestion from @geoknee

Co-authored-by: George Knee <georgeknee@googlemail.com>

* Update docs/public-docs/notices/upgrade-19.mdx

Co-authored-by: soyboy <85043086+sbvegan@users.noreply.github.com>

* Apply suggestion from @sbvegan

Co-authored-by: soyboy <85043086+sbvegan@users.noreply.github.com>

---------

Co-authored-by: George Knee <georgeknee@googlemail.com>
Co-authored-by: soyboy <85043086+sbvegan@users.noreply.github.com>
* feat(op-node): add signer grace period for unsafe block signer rotation

When the unsafe block signer address is rotated on L1, verifier nodes
can reject valid blocks during the window between the L1 update and
the runtime config reload. Add a grace period so both the old and new
signers are accepted for up to 20 minutes after a rotation is detected.

The grace period ends early when a block from the new signer is
verified, confirming the rotation is complete.

Closes ethereum-optimism#19981

Made-with: Cursor

* test(op-node): add tests for signer grace period

Tests for verifyBlockSignature grace period behavior:
- Previous signer accepted during grace period
- New signer confirmation ends grace period
- Expired grace period rejects old signer
- Third-party signer rejected even with grace active
- Normal flow still calls ConfirmCurrentSigner

Tests for RuntimeConfig grace period lifecycle:
- Signer change starts grace period
- First load (zero -> addr) does not start grace period
- ConfirmCurrentSigner clears previous
- Same signer reload preserves grace state
- Timeout expiry returns zero previous address
- Double rotation (A->B->C) tracks only most recent previous

Made-with: Cursor

* refactor(op-node): table-drive grace period gossip tests

Collapse 5 individual grace period subtests into a single table-driven
loop, reducing ~60 lines to ~20 with no behavior change.

Made-with: Cursor

* fix(op-node): address self-review on signer grace period

- Remove redundant zero-check early return in PreviousP2PSequencerAddress
- Invert ConfirmCurrentSigner guard: warn on nil current address, then
  unconditionally log + clear previous signer
- Extract signer rotation logic from Load into rotateSigner helper

Made-with: Cursor

* docs(op-node): clarify DefaultSignerGracePeriod is an arbitrary value

Made-with: Cursor

* add comment

* Update to 3h grace period

* fix(op-node): address wwared review comments on signer grace period

- gossip.go: capture current and previous signature verification errors
  and include both in the "invalid block signature" warning so operators
  can distinguish malformed signatures from legitimate rotation mismatches.
- runtime_config.go: make ConfirmCurrentSigner a no-op (no log, no write
  lock) when no grace period is active, avoiding log spam and write-lock
  traffic on every accepted block in steady state.

Made-with: Cursor

* docs(op-node): document unsafe block signer rotation grace period

Address Sebastian's review feedback on PR ethereum-optimism#20063:

- README: add an "Unsafe Block Signer Rotation" section under Failure
  modes describing the 3h grace period, why it exists, when it ends
  early, and that it is not consensus code.
- runtime_config_test: strengthen TestSignerGracePeriod_Expiry to
  cover the case where the new signer never confirms. Assert
  P2PSequencerAddress() before and after expiry, and clarify in the
  test docstring that this models the "no confirmation" path.

Made-with: Cursor

* minimize readme
…optimism#20242)

* feat(op-supernode): add supernode-level prometheus metrics

Introduces a SupernodeMetrics struct for metrics that outlive individual
virtual node restarts, served alongside per-chain VN metrics via
MetricsFanIn.AddGatherer.

Metrics added:
- supernode_virtual_node_restarts_total (counter, chain_id)
- supernode_interop_timestamps_verified_total (counter)
- supernode_interop_invalidations_total (counter, chain_id)
- supernode_interop_verified_timestamp (gauge)
- supernode_interop_round_decisions_total (counter, decision)
- supernode_interop_rewinds_total (counter)

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

* fix(op-supernode): linting fixes

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ism#19781)

* fix: update interop filter to use time.Duration flags

* chore: remove op-interop-filter gitignore

---------

Co-authored-by: Karl Floersch <karl@oplabs.co>
…m-optimism#20252)

* feat(op-challenger): age run-trace game inputs by ~16/7 days

Select a game L1 head ~16 days behind the current chain head, and a
disputed L2 block derived from an L1 block a further ~7 days earlier.
This simulates a dispute game created on a proposal just above the
anchor state and played out over the maximum game duration with clock
extensions.

Refs ethereum-optimism#19517 (interop side left unchanged).

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

* fix(op-challenger): gate aged run-trace inputs and harden young-chain fallback

Add an --age-game-inputs flag (default off) so vm-runner keeps validating
recent blocks against kona by default and operators can run a separate
deployment with aging enabled.

Make the young-chain fallback explicit instead of relying on
safedb.ErrNotFound: skip the aged candidate when the L1 chain itself is
younger than the offset, and reject any candidate whose L2 safe head is 0
(L1 block predates the L2 chain's first batch — SafeHeadAtL1Block returns
the L2 genesis safe head, not ErrNotFound, in that case). Also error when
the chosen dispute L2 block lands at or below genesis.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eum-optimism#20250)

Adds the Karst hardfork input size limit (57,600 bytes = 300 pairs × 192)
for the bn254 pairing precompile, replacing the inherited Jovian limit of
81,984 bytes at OpSpecId::KARST and OpSpecId::INTEROP. Jovian itself is
unchanged.

The reduction restores Jovian-era headroom under the EIP-7825 L1 gas cap,
accommodating the proposed EIP-7904 pairing cost increase plus plausible
variance without requiring another adjustment. op-geth and op-program are
intentionally not updated (post-Glamsterdam deprecation); op-revm and kona
are the only live paths after Karst.

Changes:
- op-revm: add KARST_MAX_INPUT_SIZE, bn254_pair::KARST, run_pair_karst;
  karst() precompile set now extends with KARST instead of inheriting JOVIAN.
  Hoist the canonical EIP-197 pairing identity test vector to a shared
  tests-module constant so Jovian and Karst tests reuse it.
- kona fpvm_evm: add BN256_MAX_PAIRING_SIZE_KARST, fpvm_bn128_pair_karst,
  accelerated_karst(); provider dispatches KARST|INTEROP to
  accelerated_karst, JOVIAN keeps accelerated_jovian.
- test_post_jovian_specs_use_jovian_precompiles: rename osaka_addrs →
  karst_addrs, restructure the address-set assertions along fork ancestry
  (KARST ≡ JOVIAN, INTEROP ≡ KARST), and reword messages so they describe
  what's actually compared (addresses match; functions may differ).

Tests:
- op-revm: 3 new tests — direct calls to run_pair_karst for valid input
  and oversize input, plus provider dispatch via OpSpecId::KARST.
- kona: 2 new bn128_pair tests mirroring the Jovian pair, plus
  test_karst_bn128_pair_enforces_karst_limit proving the KARST provider's
  dispatched function rejects a 301-pair input (57,792 bytes) with
  Bn254PairLength.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…validation (ethereum-optimism#19946)

* feat: add OPContractsManagerMigrationValidator scaffold

Add new contract and interface for validating L1 contracts after an
interop migration. Separated from OPContractsManagerStandardValidator
due to EIP-170 contract size limits.

The contract takes a reference to the existing standard validator for
shared configuration (impl addresses, l1PAOMultisig, etc.) and exposes
validateMigration/validateMigrationWithOverrides entry points.

Implementation is a stub — validation logic will be added in follow-up
commits.

* refactor: StandardValidator business logic into helper to make room for migration validation

* feat: wire MigrationValidator into StandardValidator

- Remove standardValidator back-reference from MigrationValidator
- Make MigrationValidator constructor no-arg (like StandardValidatorUtils)
- Add migrationValidator state var to StandardValidator
- Add validateMigratedChain and validateMigratedChainWithOverrides
- Deploy MigrationValidator in DeployImplementations and pass to StandardValidator

* feat: implement MigrationValidator validation logic with happy path tests

Add validation for post-interop-migration state in
OPContractsManagerMigrationValidator:

- assertValidSharedDGFShape: verify super game types registered and
  legacy types cleared on the shared DGF
- assertValidSharedSuperGame: validate game args, impl params, anchor
  root, proposer, and challenger for each super game type
- assertValidPerChainMigration: verify portal ASR, per-chain DGF
  cleared, lockbox authorization per chain
- Guard against empty chainSystemConfigs input

Add happy path test file with TestInit base contract and two core
validation tests exercising the full validation pipeline.

* feat(migration-validator): add l2SequenceNumber check to assertValidSharedSuperGame

Add l2SequenceNumber == 0 validation as error code -70, aligning with
StandardValidator convention. Renumber downstream codes: anchor root
-70 to -80, proposer -80 to -90, challenger -90 to -100.

* feat: migration checks for standard validator

* fix: pre-pr check failures in MigrationValidator tests

- Replace abi.encodeWithSignature with abi.encodeCall(IDisputeGame.l2SequenceNumber, ()) to satisfy semgrep
- Merge underscore-separated test name parts to conform to 3-4 part convention
- Fix invalid test suffix returnsErrors -> succeeds
- Add test file to function_name_validation exclusions (organized by section, not function)

* fix: bump OPContractsManagerStandardValidator version to 2.8.0

* fix: add missing SUPER_CANNON_KONA shape check and INTEROP precondition guard

H-3: Add SCKDG-SHAPE check in assertValidSuperRootDisputeGames to verify
SUPER_CANNON_KONA is registered, matching the existing SPDG-SHAPE check
for SUPER_PERMISSIONED_CANNON.

H-4: Add explicit Features.INTEROP precondition check in _migratePortal
before any state changes. Fails fast with a clear error instead of
reverting deep inside OptimismPortal2._isUsingInterop().

* fix(validators): address medium findings M-1 through M-6

M-6: Move misplaced ETHMigrated event to events section in OptimismPortal2
M-5: Extract game depth/clock magic numbers to shared file-level constants
M-4: Use IFaultDisputeGame for permissionless game type casts
M-3: Wire l1PAOMultisig override through in migration validator
M-1: Add proxy impl and ProxyAdmin checks to SharedDelayedWETH

* fix(validators): allow SUPER_CANNON in standard and migration validators

Remove assertions that SUPER_CANNON == address(0) from both
StandardValidatorUtils and OPContractsManagerMigrationValidator.
The interop migration uses SUPER_CANNON (type 4) as the
StartingRespectedGameType, so these checks incorrectly reject
valid post-migration state.

TODO(ethereum-optimism#20030): Re-add checks once SUPER_CANNON is disabled in migrator.

* fix(migrator): allow SUPER_CANNON as startingRespectedGameType

The OPContractsManagerMigrator rejected SUPER_CANNON (type 4) as a
valid startingRespectedGameType, but the Go devstack passes exactly
that during migration. This caused delegatecall to revert, failing
18 acceptance tests in memory-all-kona-op-reth and memory-all-opn-op-reth.

Add SUPER_CANNON to the allowed game types in the migrator validation.
TODO(ethereum-optimism#20030): Remove once SUPER_CANNON is disabled in migrator.

* fix: goimports formatting in multichain_proofs.go

* fix: remove wrongly-nested current-upgrade-bundle.json snapshot

* chore: remove stale TODO comment in StandardValidatorUtils

* fix: add missing != address(0) checks for non-super games in StandardValidatorUtils

* refactor: extract magic values to named constants in StandardValidatorUtils

* test: add happy path coverage for StandardValidator.validateMigratedChain

* test: add skipped SCDG-SHAPE test for SUPER_CANNON in super mode (blocked on ethereum-optimism#20030)

* chore: add commented-out SUPER_CANNON checks next to TODO(ethereum-optimism#20030) markers

* test: add per-chain DGF clearing checks (MIG-CHAIN-*-20 through -70)

* test: add coverage for validateMigratedChainWithOverrides override path

* fix: add virtual/override to _enableSuperCannonKona

* fix: bump OPContractsManagerStandardValidator version to 2.9.0

* Update test expectations for new validator checks

* Add missing ZK_DISPUTE_GAME config to migration validator test setup

* docs: add natspec to StandardValidatorUtils.internalRequire

* docs: add natspec to standardValidatorUtils state variable

* chore: clean up migration validator and address graphite review

- Drop ISemver/version() from OPContractsManagerMigrationValidator
- Remove unused ValidationOverrides struct from StandardValidatorUtils
- Name internalRequire return value as errors_ per style guide

* fix: apply challenger override in validateMigrationWithOverrides

The challenger field on ValidationOverrides was accepted by
validateMigrationWithOverrides but never applied to _input.challenger,
so callers supplying a challenger override had it silently dropped and
validation ran against the original expected address.

Add coverage for both the match and mismatch cases via
validateMigratedChainWithOverrides.

* feat(opcm): tighten migrate guards

- Require at least two chains (rename NoChains -> TooFewChains)
- Require ETH_LOCKBOX feature enabled in _migratePortal

* revert(opcm): restore NoChains guard (length == 0)

Keep ETH_LOCKBOX precondition from prior commit; single-chain migrate
path must still work for existing op-deployer integration tests.

* docs(migration-validator): clarify required interop game types

* refactor(opcm): move OPContractsManagerMigrationValidator into opcm folder

* refactor(migration-validator): derive shared DGF from first chain config

* refactor(migration-validator): split refs into SharedImplementations + SharedConfig, source from StandardValidator

* feat(contracts-bedrock): extract assertValidDisputeGame into StandardValidatorUtils

Hoists assertValidDisputeGame, assertValidDelayedWETH, and
assertValidAnchorStateRegistry out of OPContractsManagerStandardValidator
into StandardValidatorUtils so OPContractsManagerMigrationValidator can
share the same dispute-game drill-down logic.

- StandardValidatorUtils: adds DisputeGameImpls/DisputeGameConfig structs
  and public view methods that take all state-dependent values as params.
- StandardValidator: delegates to the utility via _buildDisputeGameImpls
  and _buildDisputeGameConfig helpers.
- MigrationValidator: assertValidSharedSuperGame now delegates the per-game
  drill-down to the utility while keeping the migration-unique cross-checks
  (GARGS-30 weth-discovery match, proposer/challenger overrides).
- Removes the now-redundant assertValidSharedASR and assertValidSharedDelayedWETH
  paths; the shared ASR/WETH invariants surface under both MIG-SPDG-* and
  MIG-SCKDG-* drill-downs.
- Renumbers MIG-SPDG-* / MIG-SCKDG-* error codes to align with
  StandardValidator's scheme. Tests updated accordingly.
- Adds standardValidatorUtils to SharedImplementations so MigrationValidator
  can reach it without storing its own reference.

* fix(migration-validator): preserve respectedGameType invariant + polish

- Add MIG-SASR-RGT inline check: shared ASR's respectedGameType must be a
  super game type. The drill-down via assertValidDisputeGame does not cover
  this — it's a migration-shape invariant that lived in the deleted
  assertValidSharedASR. Restore as a one-line check after shared-DGF discovery.
- Interface fixups: drop unused DisputeGameImplementation import and add the
  named return errors_ to assertValidDisputeGame to match impl.
- Replace numbered step comments in assertValidMigration with descriptive
  one-liners explaining why each block runs.
- Rename test functions from s* abbreviations (sdgf/sasr/slockbox/sdweth/anchorp/dweth)
  to shared* for clarity.

* fix: resolve rebase conflicts with develop

* fix(standard-validator): correct PreimageOracle expected version to 1.1.5
… natspec (ethereum-optimism#20348)

* chore(contracts-bedrock): drop v1 mention from _decodeDisputeGameImpl natspec

v1 dispute game support was removed in ethereum-optimism#18208; the function now only
decodes v2 game args. Also fix a stray `// @notice` so the comment is
recognized as natspec.

* chore(contracts-bedrock): regenerate semver-lock for source comment edit

* chore(contracts-bedrock): bump OPContractsManagerStandardValidator to 2.9.1
…ism#20361)

* fix(op-acceptance): default cannon-kona challenger on

Invert the op-devstack preset config flag so zero-value presets enable cannon-kona challenger support by default, then remove the now-dead opt-in helpers and acceptance-test boilerplate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: always enable cannon kona challenger support

---------

Co-authored-by: wwared <541936+wwared@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This allows unknown fields to be present in rollup config json files,
which allows a smooth transition from the recent removal of the protocol
versions field in ethereum-optimism#20317
…optimism#20374)

Fail PR target branch resolution when the GitHub API cannot return a usable base branch instead of falling back to CIRCLE_BRANCH. Also fail semver diff checks when the upstream ref is missing so missing fetches cannot look like an unchanged lockfile.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: merge with dev

* feat: merge with dev

* fix: merge with dev

* fix: merge with dev

* feat: op deployer zk

* feat: op deployer zk

* feat: acceptance tests and fix

* fix: lint

* feat: merge with develop

* fix: comments and merge

* fix: comments and merge

* fix: lint

* fix: comments
…timism#20157)

* feat(kona): execute NUT bundles at Karst fork activation

Add build.rs that reads karst_nut_bundle.json at compile time and
generates Rust code to construct the NutBundle. The derive pipeline
now adds upgrade gas to the block gas limit at fork activation,
matching op-node's gas accounting behavior.

* test(op-e2e): add generic NUT bundle activation block action test

Verifies that every fork with an embedded NUT bundle produces an
activation block containing exactly the bundle's deposit transactions.
Discovery uses forks.All + derive.UpgradeTransactions, so future forks
with NUT bundles are covered automatically without test changes.

* fix(kona): mirror NUT bundles into kona-hardforks crate

The Docker build context for kona images is scoped to rust/, so build.rs
cannot reach op-core/nuts/bundles/ via ancestor walk and the kona-client/
host/node images fail to build. Keep a byte-identical copy inside the
crate, written and verified automatically by the existing snapshot/lock
tooling so the two sources cannot silently drift.

* Revert "fix(kona): mirror NUT bundles into kona-hardforks crate"

This reverts commit 941e15a.

* build(docker): pass NUT bundles as named context to kona images

The kona-hardforks build.rs walks ancestors of CARGO_MANIFEST_DIR to find
op-core/nuts/bundles/ during compilation. The kona-{client,host,node}
images scope their Docker context to rust/, so the ancestor walk can't
reach op-core/ and the images fail to build.

Pass op-core/nuts/bundles as an additional named BuildKit context
(nuts-bundles) and copy it into /workspace/op-core/nuts/bundles so the
ancestor walk succeeds. Keeps the primary rust/ context small and avoids
mirroring the bundle JSON into the crate tree.

* test(op-e2e): tighten NUT bundle activation test coverage

Iterate forks.From(forks.Karst) with a NoError assertion on
derive.UpgradeTransactions rather than silently skipping forks whose
bundle fails to load, so a broken bundle surfaces as a test failure.

Also assert every tx in the activation block has a successful receipt
so a reverted upgrade tx can't hide behind the byte-equality check.

* refactor(kona-hardforks): extract impl_hardfork_from_bundle macro

The Hardfork impl for each NUT-bundle-backed fork is identical boilerplate
(decode, EIP-2718 encode, sum gas). Move that pattern into a single
internal macro so adding future forks is a one-liner next to the
build-script-generated constructor.

* refactor(kona-hardforks): make build.rs testable with shared codegen

Split parsing + codegen into build_helpers.rs, included via #[path] from
both the build script and a new integration test. Use anyhow to carry
error context up to a single panic in main. The integration test runs
the generator on a fixture JSON and asserts the exact Rust source output,
so codegen changes surface as a test failure rather than a downstream
compile error.

* test(op-e2e): add karst_fork_test with pre/post impl slot assertion

Follows the per-fork test convention from ecotone/fjord/isthmus/holocene.
Asserts the EIP-1967 implementation slot of representative predeploy
proxies (L1Block, GasPriceOracle) changes across Karst activation — a
Karst-specific smoke test that the bundle's proxy upgrades took effect.

Not generalized into the NUT bundle activation test because it does not
hold for future forks that may not upgrade proxies.

* refactor(op-core/forks): add Prev() sibling to Next()

Share the initialization between the next/prev maps and replace the
manual forks.All index walk in nut_bundle_activation_test.go with
forks.Prev(fork). Keeps neighbor-navigation centralized in the forks
package rather than re-derived at call sites.

* test(op-e2e): run fault-proof program and fold karst assertions in

The generic activation test was missing the core RunFaultProofProgram
call (preceded by BatchMineAndSync to advance the safe head), so it
only exercised the sequencer, not the proof pipeline it lives under
actions/proofs/ to validate.

Fold the Karst-specific impl-slot assertions back in via a switch on
fork name, establishing the pattern for future forks to register their
own post-activation checks alongside the universal ones. Delete
karst_fork_test.go.

* test(op-e2e): tighten activation-block checks

Use RollupCfg.IsActivationBlockForFork instead of the unguarded
IsActivationBlock(time-blockTime, time) — the helper avoids a uint64
underflow when actHeader.Time < blockTime.

Assert the safe head lands exactly on the activation block rather than
just past it; BatchMineAndSync adds no new L2 blocks, so equality is
the right invariant.

* test(op-core/forks): add unit tests for Next, Prev, From

Table-driven coverage of edge cases (first, middle, last, unknown)
plus a NextPrev/PrevNext inverse check that guards against the two
maps drifting out of sync.

* perf(kona-hardforks): cache NUT bundle in OnceCell

Generated bundle constructors now wrap construction in a function-scoped
once_cell::sync::OnceCell and return &'static NutBundle. The first call
allocates and stores the bundle; subsequent calls return a cached
reference. Activation-path calls to txs() and upgrade_gas() no longer
reallocate on each invocation.

Consumers are unchanged — method calls auto-deref through the reference.

* style(kona-hardforks): apply nightly rustfmt and clippy fixes

cargo fmt -- collapses a few multi-line statements onto one line per
the use_small_heuristics = "Max" rustfmt.toml setting.

cargo clippy -- replaces a match-on-Option in the bundle codegen with
Option::map_or_else per the option_if_let_else lint.

* build(docker): mount NUT bundles into kona-cannon-prestate build

The cannon-repro.dockerfile's client-build stage runs cargo build on the
kona workspace, which triggers the kona-hardforks build script. That
script walks ancestors of CARGO_MANIFEST_DIR looking for op-core/ and
panicked because the context only contained kona/. Pull in
op-core/nuts/bundles from the existing 'monorepo' named context so the
ancestor walk succeeds — same pattern as the kona-node/host/client
image fix.

* fix: address nightly rustfmt and go-lint findings

- stateful.rs: collapse the u64::from_be_bytes call onto one line per
  nightly rustfmt's use_small_heuristics = "Max" setting. Introduced
  by the original Karst upgrade_gas change; caught by rust-fmt CI.
- nut_bundle_activation_test.go: use bigs.Uint64Strict(bigInt) instead
  of bigInt.Uint64() per the repo's bigint custom golangci-lint rule.

* fix(kona-hardforks): use OnceBox to avoid critical-section dep on no_std

sync::OnceCell with the critical-section feature requires an impl of
_critical_section_1_0_{acquire,release} to be linked in, which the
MIPS64 cannon and RISC-V asterisc fault-proof VM targets don't provide.
This broke kona-client on those targets.

Switch the generator to emit once_cell::race::OnceBox, which uses a
single AtomicPtr for the cache slot — lock-free and requires no
critical-section impl. The NutBundle is heap-allocated via Box on first
call and then cached as &'static NutBundle for all subsequent calls.
Enables the `race` feature on once_cell.

* build(kona): mount op-core/nuts/bundles in build-cannon-client recipe

The cannon-target docker run mounted only the rust/ directory at
/workdir, leaving /workdir/op-core/ absent. The kona-hardforks build
script walks ancestors of CARGO_MANIFEST_DIR looking for op-core/ and
panics when it can't find the directory.

Add a second read-only volume mount that exposes op-core/nuts/bundles
at /workdir/op-core/nuts/bundles, mirroring the named-context fix used
by the kona-node/host/client image bake and the cannon-prestate
Dockerfile. Unblocks kona-build-fpvm-cannon-client and
kona-host-client-offline-cannon.

* test(op-e2e): tombstone forks-without-NUT-bundle exception list

Interop is post-Karst chronologically but still uses the legacy
hardcoded upgrade-transactions path rather than a JSON NUT bundle, so
require.NoError on derive.UpgradeTransactions(forks.Interop) failed.

Replace the simple no-error assertion with a bidirectional check
against forksWithoutNUTBundle = {forks.Interop: true}. The exception
list cannot go stale silently:

- Fork lacks bundle and isn't on the list → test fails (forces a
  decision: implement the bundle or update the list).
- Fork has bundle and is on the list → test fails with
  "remove it from forksWithoutNUTBundle".

When Interop gets its NUT bundle in a follow-up PR, this test will
fail until forks.Interop is removed from the exception list, ensuring
the list stays in lockstep with the codebase.

* chore(kona-hardforks): propagate std feature to new dependencies

Zepter's `lint propagate-feature` check requires kona-hardforks's
`std` feature to also enable the `std` feature of every dependency
(runtime, build, and dev) that has one. Add propagation entries for
the deps introduced by the NUT bundle work: anyhow, once_cell, serde,
and serde_json.

* fix(op-e2e): wire KarstTime into rollup config builder

Why: TestActivationBlockNUTBundle is the first test to probe Karst
activation; without KarstTime in the rollup config, IsActivationBlockForFork
never fires for Karst.

* fix(kona-hardforks): probe for bundle file in build.rs ancestor walk

Why: Docker bind-mounts auto-create empty op-core/ ancestor stubs on the
host; an is_dir() check picks those up before reaching the real op-core
at the monorepo root. Probe for the karst bundle file directly.

* feat(op-core/nuts): Update karst bundle snapshot

* fix(docker): include nut bundles for kona host builds

* fix(kona-hardforks): update karst test expectations

---------

Co-authored-by: maurelian <maurelian@protonmail.com>
…-optimism#20366)

* chore(rust/scr): update superchain registry for rust crates

* chore(rust/scr): update superchain registry to cc07e96d91a1647cbce7eef6572098bece1f7fca for rust crates
…#20371)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@louisliu2048 louisliu2048 changed the title Sync 1170 Sync upstream version v1.17.0 Apr 30, 2026
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.