Skip to content

feat(l2): let the OnChainProposer owner replace the SP1 verifier - #7412

Merged
ilitteri merged 6 commits into
mainfrom
feat/l2-ocp-upgrade-sp1-verifier
Oct 15, 2026
Merged

ilitteri merged 6 commits into
mainfrom
feat/l2-ocp-upgrade-sp1-verifier

Conversation

@ilitteri

@ilitteri ilitteri commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

SP1_VERIFIER_ADDRESS can only be set in initialize. A new SP1 version can wrap its proofs in a different Groth16 circuit, and an SP1VerifierGroth16 only accepts proofs of its own circuit: it checks the proof's first four bytes against its VERIFIER_HASH (0xa4594c59 for SP1 5's v5.0.0, 0x4388a21c for SP1 6's v6.1.0) and reverts with WrongVerifierSelector otherwise, whatever verification key is registered. A deployment that points at a verifier of its own, which is what the deployer creates by default, so cannot move its prover to a new SP1 version. Deployments on Succinct's SP1VerifierGateway are not affected: the gateway routes each proof to the verifier of its version.

Description

  • Both OnChainProposers, the regular and the based one, get upgradeSP1Verifier(address). It is onlyOwner like upgradeSP1VerificationKey, sets SP1_VERIFIER_ADDRESS and emits SP1VerifierUpgraded. It refuses address zero while SP1 proofs are required outside Aligned mode, which verifies SP1 proofs through the AlignedProofAggregator instead. It also refuses to run while a committed batch is unverified (UnverifiedBatchesPending(lastVerified, lastCommitted), declared in both interfaces): those batches' proofs only verify against the verifier they were proven for, and the based proposer has no revertBatch to recover them. It refuses a non-zero address without code (Sp1VerifierHasNoCode), which would otherwise only show up when the next proof fails.
  • The based proposer's initializer now refuses a missing SP1 verifier while SP1 proofs are required outside Aligned mode, as the regular one already did. The deployer always passes or deploys one in that configuration.
  • Storage is unchanged. The runtime code goes from 19,037 to 19,420 bytes, and from 13,415 to 13,967 for the based one.
  • tooling/hardhat/test/upgrade-sp1-verifier.test.js covers the setter on both proposers in the Hardhat suite the Upgradeability workflow runs: the swap once every batch is verified, the refusal while one is not, owner-only access, address zero and addresses without code, the one-call upgradeToAndCall path reverting as a whole while a batch is pending, and the initializer check.
  • An existing deployment can upgrade its OnChainProposer and set the verifier in one Timelock call, upgradeToAndCall(newImplementation, upgradeSP1Verifier(newVerifier)): the upgrade runs the setter with the Timelock as the caller, so onlyOwner passes. It reverts as a whole if a batch is still unverified.
  • The upgrades fundamentals explain when the verifier has to be replaced, that the contract enforces the drain, and that gateway deployments have nothing to replace.

This was run on a GPU host from v25.0.0 and from v29.0.1 to a build with SP1 6.9.0 (#7401). One emergencyExecute of upgradeToAndCall(newImplementation, upgradeSP1Verifier(v6.1.0 verifier)) moved SP1_VERIFIER_ADDRESS from the v5.0.0 verifier to the v6.1.0 one, the new release's SP1 6 proofs were verified, and the L2 integration tests passed. Those runs drained every batch before the switch, so the guard added since does not change their path.

Checklist

  • Updated STORE_SCHEMA_VERSION (crates/storage/lib.rs) if the PR includes breaking changes to the Store requiring a re-sync.

@ilitteri
ilitteri requested a review from a team as a code owner October 14, 2026 19:54
@ilitteri ilitteri added the L2 Rollup client label Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

rpc-compat log-bearing cases excluded

Where: KNOWN_EXCLUDED_TESTS in .github/scripts/check-hive-results.sh counts out
eight hive rpc-compat cases — the four eth_getLogs cases, eth_getBlockReceipts/get-block-receipts-latest,
and three eth_getTransactionReceipt cases. They are exactly the cases whose recorded
response contains at least one log object; every case with an empty log array still runs.
Note this leaves eth_getLogs with no rpc-compat coverage at all, since all four of its
cases are in the set.

Why: ethrex populates blockTimestamp on log objects, as geth, besu, nethermind, reth
and erigon all do. hive's rpc-compat compares responses byte-exactly (jsondiff.FullMatch;
the lenient checkJSONStructure path applies only to cases upstream marks speconly), and
the corpus is pinned to execution-apis d08382ae (2025-02-10), whose recordings predate the
field — it entered the schema in execution-apis#639 and the fixtures in #846 (2026-07-22).
So the extra key cannot match, and this is a property of the pin rather than of the response.

The pin cannot move, and this is not temporary. The pin sits one commit before
execution-apis#627, which moved the test chain to a pre-merge genesis: the current corpus has
~36 proof-of-work blocks before its terminal total difficulty. ethrex does not support
pre-merge chains and will not, so importing that chain.rlp fails at block 1 —
validate_block_header has no pre-London base-fee path. Every revision carrying
blockTimestamp in its fixtures also carries that chain, so there is no revision that
satisfies both. Nor can the corpus be patched locally: rpc-compat's Dockerfile clones
ethereum/execution-apis by hard-coded URL, so the branch buildarg cannot point at a fork.

Coverage: the field itself is pinned by
block_timestamp_is_on_the_log_and_not_on_the_receipt in
crates/networking/rpc/types/receipt.rs, which asserts it is present on each log and absent
from the receipt level.

Removal: delete the entries if ethrex ever gains pre-merge chain import, or if upstream
marks these cases speconly so they are type-checked instead of compared byte-for-byte.


The stateless schema id does not identify the encoding

Where: STATELESS_INPUT_SCHEMA_ID in crates/common/types/stateless_ssz.rs.

Upstream keeps the stateless input schema id at 0x1501
(fork_index 0x15 << 8 | revision 0x01) across incompatible body changes. Four
encodings have now shipped under it: tests-zkevm@v0.6.2, then #3248 + #3278,
then #3356, which moved state, codes and public_keys from SszList to
ProgressiveList, then #3652 in tests-zkevm@v21.0.1, which removed
public_keys outright without bumping the revision. ethrex speaks the last one.

The consequence is that the 2-byte prefix cannot be used to detect a stale or
mismatched bundle. A wrong-dialect input is accepted by the id check and then
fails later — in SSZ decode, or on a root that does not match — rather than being
rejected up front for what it is. only_amsterdam_schema_id_decodes therefore
proves less than its name suggests.

Worth raising upstream: a revision field that does not move across a body change
provides no version negotiation at all.


ZisK guest program hash changes with the unsync_cell gate

Where: crates/common/types/block.rs, transaction.rs.

The gate on the single-threaded unsync_cell::OnceCell moved from
all(feature = "eip-8025", target_arch = "riscv64") to
all(feature = "zisk", target_arch = "riscv64") when the eip-8025 feature was removed.

The guest ELFs were previously built --features "<zkvm>-build-elf,ci", which never enabled
eip-8025, so they compiled the atomic once_cell variant. bin/zisk/Cargo.toml does enable
ethrex-common/zisk, so the ZisK guest now compiles the unsafe impl Sync cell instead.
That changes the ELF bytes and therefore the program hash and verification key.

This is intended (the guest is single-threaded, so the unsync cell is sound and cheaper), but it
is a VK change rather than a no-op refactor, and the diffstat presents it as a file rename
(eip8025_cell.rs → unsync_cell.rs). Anyone pinning a ZisK VK across this change must
re-register it. The stateless-validator crate now forwards ethrex-common/zisk from its own
zisk feature so the two ZisK guests do not disagree on the cell type.


Release signing key is an unprotected repository secret

Where: .github/workflows/tag_release.yaml.

MINISIGN_SECRET_KEY is a plain repository secret. There is no environment: on
finalize-release or dry-run-release-assets, and gh api repos/lambdaclass/ethrex/rulesets
shows only branch-targeted rulesets, so the github.ref_type == 'tag' condition is a workflow
check rather than an enforced boundary: anyone who can push a tag can reach the signing key.

This is a repository-settings change, not a code change, so it is recorded here rather than
fixed in the tree. Recommended:

  1. Move MINISIGN_SECRET_KEY / MINISIGN_PASSWORD into a GitHub Environment with required
    reviewers, and add environment: to the two jobs that sign.
  2. Add a ruleset targeting refs/tags/v* restricting who may create release tags.

Until then, the compromise of that key is silent and durable: signatures would still verify
against the committed .github/minisign.pub.

@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

I'll review this PR which adds an upgradeSP1Verifier function to allow replacing the SP1 verifier contract address in the OnChainProposer.

Security and Correctness Review

Critical Issue: Missing Zero-Address Check for ALIGNED_MODE

File: crates/l2/contracts/src/l1/OnChainProposer.sol:215 and crates/l2/contracts/src/l1/based/OnChainProposer.sol:210

if (REQUIRE_SP1_PROOF && !ALIGNED_MODE && new_verifier == address(0))
    revert MissingSp1Verifier();

The validation logic has a subtle bug. When REQUIRE_SP1_PROOF == true and ALIGNED_MODE == true, the condition short-circuits to false, allowing new_verifier = address(0) to be set. However, in Aligned mode, proofs are verified through AlignedProofAggregator for SP1 proofs specifically — but the SP1_VERIFIER_ADDRESS state variable may still be read elsewhere or used in future code paths.

More critically: if REQUIRE_SP1_PROOF == false, any address including zero is allowed. This is intentional for disabling SP1, but there's no symmetric check preventing a non-zero address when SP1 is disabled, which could be misleading.

Recommendation: Add an explicit check that prevents setting a non-zero verifier when SP1 is not required, or document this asymmetry:

if (REQUIRE_SP1_PROOF && !ALIGNED_MODE && new_verifier == address(0))
    revert MissingSp1Verifier();
// Optional: prevent confusion
if (!REQUIRE_SP1_PROOF && new_verifier != address(0))
    revert UnnecessarySp1Verifier(); // or emit warning

Medium Issue: No Event for Previous Verifier Address

Files: Both OnChainProposer.sol implementations

The SP1VerifierUpgraded event only emits the new verifier address. For audit trails and off-chain monitoring, the previous verifier address is valuable for tracking the full upgrade history.

Recommendation: Consider emitting the old verifier:

event SP1VerifierUpgraded(address indexed previousVerifier, address indexed newVerifier);

Minor Issue: Inconsistent Parameter Naming Convention

Files: IOnChainProposer.sol interfaces vs. implementation

The interface uses new_verifier (snake_case) while the event uses newVerifier (camelCase). The Solidity style guide recommends camelCase for parameters. This is inconsistent with upgradeRISC0VerificationKey which uses new_vk (snake_case), so this follows existing convention — acceptable but worth noting.

Documentation Review

File: docs/l2/fundamentals/upgrades.md

The documentation is clear and well-structured. One issue:

Line 33-34: The statement "If that is Succinct's SP1VerifierGateway... there is nothing to replace" is accurate but could mislead operators. The Gateway itself could need upgrading if Succinct deploys a new version. Consider adding:

"Note: The SP1VerifierGateway address itself may change in future SP1 releases. Monitor Succinct's documentation for gateway upgrades."

Positive Observations

  1. Proper access control: onlyOwner modifier is correctly applied
  2. Event emission: Follows the pattern of other upgrade functions
  3. Aligned mode handling: Correctly recognizes that Aligned mode doesn't use direct SP1 verification
  4. Documentation completeness: The migration path for pre-v9 deployments with upgradeToAndCall is well-documented
  5. State consistency: The function doesn't modify SP1_VERIFICATION_KEY_HASH, maintaining separation between verifier contract and verification key concerns

Summary

Severity Issue Location
Low Missing zero-address check edge case in Aligned mode OnChainProposer.sol (both)
Low Event lacks previous verifier for audit trail IOnChainProposer.sol (both)
Info Gateway upgrade path not documented upgrades.md

The PR is fundamentally correct and safe for merge. The validation logic properly protects against the critical case (losing verifier when proofs are required). Consider addressing the informational items in follow-up.


Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

Review: upgradeSP1Verifier in OnChainProposer (regular and based)

I reviewed the Solidity changes and did not compile them or run any tests. The commit is the whole repo squashed into one, so I only looked at the files that touch this feature.

Overall: The change is small and consistent. I found no blocking issues.

  • Access control: upgradeSP1Verifier is onlyOwner, the same as upgradeSP1VerificationKey. That fits the Timelock-owned upgrade flow described in the commit message.
  • Storage layout: Only a function and an event were added. SP1_VERIFIER_ADDRESS already exists, so storage is unchanged and the UUPS upgrade is safe.
  • Zero-address guard:
    • In OnChainProposer.sol:216 and based/OnChainProposer.sol:211 the check is REQUIRE_SP1_PROOF && !ALIGNED_MODE && new_verifier == address(0).
    • It mirrors the check in initialize at OnChainProposer.sol:154.
    • It lets the owner clear the verifier in Aligned mode, where SP1 proofs go through the aggregator instead.
  • Interfaces: Both IOnChainProposer interfaces declare the event and the function, and the MissingSp1Verifier error already exists in both. The @inheritdoc tags are correct.

Minor points (non-blocking):

  1. Asymmetric validation in the based contract.
    • The based initialize (based/OnChainProposer.sol:148) does not reject a zero sp1verifier, but the new upgrade function does.
    • Rejecting zero on upgrade is the safer behavior.
    • If you want the two paths consistent, add the same check to the based initialize. That would change behavior for existing deployment scripts, so it may be better as a separate change.
  2. No contract check on the new address. A non-zero EOA or a wrong contract would pass. Every verifyProof call would then fail, or an EOA would succeed on an empty return. In the EOA case ISP1Verifier(addr).verifyProof would revert on the extcodesize check, so batches fail closed and are not accepted without a proof. Adding new_verifier.code.length > 0 would catch a typo at upgrade time. This is optional, since the owner is a Timelock and the call is reviewable before it executes.
  3. Naming. new_verifier uses snake_case, which matches the existing commit_hash and new_vk parameters. It is consistent, so no change is needed.
  4. Tests. I could not confirm whether tests cover the new function. Please make sure there are tests for:
    • a non-owner call reverting;
    • a zero address reverting when SP1 is required and Aligned mode is off;
    • a zero address being accepted in Aligned mode, or when SP1 is not required;
    • the event being emitted;
    • the upgradeToAndCall(impl, abi.encodeCall(upgradeSP1Verifier, ...)) path, in both contract variants.

Looks good to merge once the tests are in place.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

Found one concrete issue.

  • crates/l2/contracts/src/l1/based/OnChainProposer.sol:147
    The based variant still allows initialization with REQUIRE_SP1_PROOF == true, ALIGNED_MODE == false, and sp1verifier == address(0). This PR adds upgradeSP1Verifier(), but it does not close that existing hole in initialize(), unlike the main contract at crates/l2/contracts/src/l1/OnChainProposer.sol:154. In that misconfigured state, later verifyBatches() calls hit ISP1Verifier(address(0)).verifyProof(...) and revert as InvalidSp1Proof, effectively bricking verification behind a misleading proof error. Recommend mirroring the main contract’s guard in the based initializer: reject zero sp1verifier whenever SP1 proof verification is required and not running in aligned mode.

Otherwise, the change looks reasonable:

  • crates/l2/contracts/src/l1/OnChainProposer.sol:214 and crates/l2/contracts/src/l1/based/OnChainProposer.sol:209
    The new setter is properly onlyOwner, preserves the aligned-mode exception, and emits an event for observability.
  • crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol:100 and crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol:96
    Interface updates are consistent with the implementation.
  • No EVM gas-accounting, consensus, RLP, or state-transition logic is touched by this PR. The impact is operational/configurational around proof verification only.

Automated review by OpenAI Codex · gpt-5.4 · custom prompt

ilitteri added a commit that referenced this pull request Oct 14, 2026
…) and main, into this branch, so this pull request stacks on them
@ilitteri
ilitteri changed the base branch from main to fix/l2-commit-sealed-batch-prover-input October 14, 2026 20:07
@ilitteri
ilitteri added this pull request to stack #7416 October 14, 2026 20:07
@github-actions

Copy link
Copy Markdown

Lines of code report

Total lines added: 24
Total lines removed: 0
Total lines changed: 24

Detailed view
+--------------------------------------------+-------+------+
| File                                       | Lines | Diff |
+--------------------------------------------+-------+------+
| ethrex/crates/l2/sequencer/l1_committer.rs | 1386  | +24  |
+--------------------------------------------+-------+------+

}

/// @inheritdoc IOnChainProposer
function upgradeSP1Verifier(address new_verifier) public onlyOwner {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Worth guarding this against pending batches. Proofs of batches committed under the old version only verify against the old verifier, so swapping while lastVerifiedBatch != lastCommittedBatch strands them. On the regular contract the way out is pause, revertBatch and re-commit; here on the based one there is no revertBatch and no pause, so the only remedy is swapping the old verifier back. The runbook's step 1 says to drain first, but the contract can enforce it:

if (lastVerifiedBatch != lastCommittedBatch)
    revert UnverifiedBatchesPending(lastVerifiedBatch, lastCommittedBatch);

Same in the regular proposer, with the error declared in both interfaces. It also makes the one-shot upgradeToAndCall(impl, upgradeSP1Verifier(...)) revert when batches are pending, which is the right outcome: that is exactly the state the swap must not happen in.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 1850f58: both proposers now revert upgradeSP1Verifier with UnverifiedBatchesPending(lastVerified, lastCommitted), declared in both interfaces, so upgradeToAndCall(impl, upgradeSP1Verifier(...)) reverts as a whole in that state too. One trade-off: if the new verifier turns out wrong and batches get committed after the swap, the guard also blocks fixing it with the setter. The regular proposer recovers with pause, revertBatch, the setter and a re-commit; the based one would need an implementation upgrade. That seems the better trade, since a premature swap is the likelier mistake. To narrow the wrong-verifier case, 30648b9 also refuses an address without code, and 2fe1aaa adds Hardhat tests for the setter on both proposers, the guard and the upgradeToAndCall path included.

ilitteri added a commit that referenced this pull request Oct 14, 2026
ilitteri added a commit that referenced this pull request Oct 14, 2026
ilitteri added a commit that referenced this pull request Oct 14, 2026
ilitteri added a commit that referenced this pull request Oct 14, 2026
@ilitteri
ilitteri requested a review from jrchatruc October 14, 2026 21:36
@ilitteri
ilitteri force-pushed the feat/l2-ocp-upgrade-sp1-verifier branch from 2fe1aaa to 472c4fd Compare October 14, 2026 21:59
@ilitteri
ilitteri added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was unmerged and was not in the queue Oct 15, 2026
@ilitteri
ilitteri added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was unmerged and was not in the queue Oct 15, 2026
…ther build sealed. The committer re-sends an already sealed batch as it is, and prover inputs are stored per version, so a batch sealed by the build that ran before an upgrade, or received from the sequencer this node synced from, had no input for the new version: its provers got no work and verification stopped. This happens when an upgrade has to re-commit batches, for example after reverting batches whose proofs the verifier no longer accepts. The input is generated before the checkpoint moves to the batch's end, from the same state the batch starts at.
…w SP1 version can wrap its proofs in a different Groth16 circuit, and the SP1VerifierGroth16 a deployment points at rejects those (WrongVerifierSelector) whatever verification key is registered, while SP1_VERIFIER_ADDRESS could only be set in initialize. upgradeSP1Verifier(address), onlyOwner like upgradeSP1VerificationKey, sets it and emits SP1VerifierUpgraded; it refuses address zero while SP1 proofs are required outside Aligned mode. Both the regular and the based OnChainProposer get it; storage layout is unchanged. An existing deployment can upgrade its OnChainProposer and set the verifier in one Timelock call, upgradeToAndCall(newImplementation, upgradeSP1Verifier(newVerifier)). The upgrades fundamentals explain when the verifier has to be replaced, and that deployments on Succinct's SP1VerifierGateway have nothing to replace.
…ied. Proofs of batches committed before the swap only verify against the verifier they were proven for, so a swap with lastVerifiedBatch below lastCommittedBatch strands them: the regular OnChainProposer can recover with pause, revertBatch and a re-commit, but the based one has neither, so its only way out was swapping the old verifier back. Both proposers now revert upgradeSP1Verifier with UnverifiedBatchesPending(lastVerified, lastCommitted), declared in both interfaces, which also makes a one-shot upgradeToAndCall(implementation, upgradeSP1Verifier(verifier)) revert as a whole in that state. The upgrades fundamentals say the contract enforces the drain. Runtime code grows by 60 bytes to 19,353, and by 62 to 13,775 for the based proposer.
…t code. A mistyped address was accepted and only showed up when the next proof failed to verify, and now that the setter refuses to run while a batch is unverified, batches committed after such a swap can no longer be rescued by swapping again: the regular proposer has to pause and revert them first, and the based one needs a new implementation. Both proposers revert upgradeSP1Verifier with Sp1VerifierHasNoCode for a non-zero address with no code; address zero keeps its existing rules.
…ifier, as the regular one does. It accepted address zero while SP1 proofs were required outside Aligned mode, leaving every verification to fail with InvalidSp1Proof, while upgradeSP1Verifier refuses the same address. The deployer always passes or deploys a verifier in that configuration, so existing deployments are unaffected.
… the Upgradeability workflow runs. Each proposer is deployed behind a UUPS proxy and initialized, and the tests check that the setter replaces the verifier once every batch is verified, refuses while a batch is not, is owner-only, refuses address zero unless in Aligned mode and an address without code, works inside upgradeToAndCall and reverts that whole call while a batch is unverified, and that the initializer refuses a missing SP1 verifier. Committing real batches needs a bridge, blobs and a sequencer, so the tests write the two batch counters at the slots the compiler reports for them.
@ilitteri
ilitteri force-pushed the feat/l2-ocp-upgrade-sp1-verifier branch from 472c4fd to a6a69b6 Compare October 15, 2026 19:33
@ilitteri
ilitteri added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was unmerged and was not in the queue Oct 15, 2026
Base automatically changed from fix/l2-commit-sealed-batch-prover-input to main October 15, 2026 21:11
@ilitteri
ilitteri added this pull request to the merge queue Oct 15, 2026
Merged via the queue into main with commit 3605f82 Oct 15, 2026
84 of 86 checks passed
@ilitteri
ilitteri deleted the feat/l2-ocp-upgrade-sp1-verifier branch October 15, 2026 21:51
MysticRyuujin pushed a commit to MysticRyuujin/ethrex that referenced this pull request Oct 16, 2026
…ss#7401)

**Motivation**

The L2 prover and its SP1 batch guest are on SP1 5.0.8, and the newest
SP1 is 6.9.0. Succinct froze the SP1 v5.0.0 Groth16 route on its
verifier gateway on Oct 9, and Aligned is moving its aggregation mode to
SP1 6. 6.9.0 produces the same verification artifacts as 6.8.1, the
version Aligned is targeting (circuit version `v6.1.0`). It is also past
GHSA-p7v2-jjv3-8p8f, which affects SP1 6.0.0 through 6.8.0.

**Description**

Prover (`crates/prover`):
- `sp1-sdk` moves to `=6.9.0` with its `blocking` API, and `sp1-prover`
is no longer a direct dependency.
- The backend sets the guest up once with `setup` and keeps the proving
key; proving is `prove(&pk, stdin).mode(..).run()`.
- SP1 6's blocking API drives its own tokio runtime with `block_on`,
which panics on a thread that is already running one. The prover calls
the backend from async code, so the SDK calls (setup, execute, prove)
now run on a scoped thread outside the runtime.
- With `gpu`, it uses the `CudaProver`, which runs SP1 6's local
`sp1-gpu-server`. SP1 6 has no remote prover server, so `--sp1-server` /
`ETHREX_SP1_SERVER` is now ignored with a warning, and its help text
says so.
- `sp1-sdk` 6 has no default features, so the network and AWS KMS
dependencies no longer come in through the prover.

Guest (`crates/guest-program`):
- `sp1-zkvm`, `sp1-build` and the build-time `sp1-sdk` move to `=6.9.0`.
SP1 6 builds a RISC-V 64-bit guest, and the reproducible build uses the
`v6.9.0` image.
- The ELF and verifying-key files keep their `riscv32im-succinct-zkvm-*`
names, because release assets and scripts refer to them.
- The precompile patches move to the `sp1-6.2.0` tags that SP1 6.9.0's
own examples use. The `ecdsa` patch goes: the `k256` patch now brings
its own.
- `kzg-rs` is patched to Succinct's `v0.2.8-sp1-6.2.0` tag. The
crates.io release would otherwise pull `sp1-lib` 5's syscall wrappers
into an SP1 6 guest.
- The lambdaclass `bls12_381` forks are dropped. The guest only used
them for `G1Affine`/`G2Affine::new_unchecked` and the exported
`Fp`/`Fp2`. The field types are reachable through upstream's
`MapToCurve::Field`, as the OpenVM stateless provider already does. Once
both coordinates pass the canonical check, `from_uncompressed_unchecked`
decodes exactly the same point, because none of its flag bits can be
set. `guest-program` now uses `bls12_381` 0.8.0 from crates.io. The SP1
guest patches it to `sp1-patches`' `patch-0.8.0-sp1-6.2.0`, the copy
`kzg-rs` already uses, so it builds one `bls12_381` instead of two. The
RISC0 guest's lockfile still names the old fork: it cannot be resolved
until risc0 publishes a `c-kzg` 2.1.8 or newer tag, which is also why CI
skips it.
- `sp1-zkvm` 6 has no `embedded` feature, because its embedded allocator
is now the default, so the `l2` feature no longer enables it.

Contracts and deployment:
- The deployer compiles `SP1VerifierGroth16` from sp1-contracts
`v6.1.0`, the circuit version SP1 6.9.0 proves against.
- An existing deployment's SP1 5 verifier rejects SP1 6 proofs whatever
key is registered: they start with a different verifier selector
(`0x4388a21c` instead of `0xa4594c59`) and come from a different Groth16
circuit. A deployment with a verifier of its own needs lambdaclass#7412 to replace
it; one on Succinct's `SP1VerifierGateway` only registers the new key,
since the gateway routes SP1 6 proofs to its `v6.1.0` verifier.

Aligned mode:
- Aligned mode is unavailable for now, as agreed with the Aligned team,
who are releasing SP1 6 support soon.
- Their SDK (`agg_mode_sdk`) still uses `sp1-sdk` 5 types and verifies
SP1 5 proofs, so it cannot take this guest's proofs or program.
- `crates/l2` keeps `sp1-sdk` 5.0.8 for that glue only. A sequencer
started with `--aligned` now stops at startup with an error that
explains this, and the Aligned deployment guide has a warning.

OpenVM L2 backend:
- The OpenVM backend of the L2 prover is removed: `--backend openvm` and
the `openvm` features of `ethrex-prover` and `ethrex-l2-prover`.
- OpenVM 1.5 requires `p3-field =0.4.1` and SP1 6 requires
`=0.4.3-succinct`. Both are crates.io releases in the same semver range,
so no lockfile can hold both.
- The OpenVM guest stays; only the prover backend goes. The docs already
list OpenVM's L2 prover as planned.

CI and docs:
- `install-sp1` installs 6.9.0, and `protoc`, which SP1 6's
`sp1-prover-types` build script needs.
- The GPU workflow stops a stale `sp1-gpu-server` instead of removing
SP1 5's `sp1-gpu` container, which no longer exists.
- The GPU workflow enables GPU persistence mode before the test. The SDK
waits about one second for the `sp1-gpu-server` it starts to open its
socket. Without persistence mode, the runner's driver takes about two
seconds to initialize the GPU, so the prover failed at startup.
- The GPU workflow runs the prover with a smaller SP1 proving pipeline
(`SP1_WORKER_*`, one worker per stage), which `sp1-gpu-server` reads
from the prover's environment. With SP1's defaults, the server peaked at
42.4 GB of host memory on the 44 GB runner and was killed. With these
settings it peaked at 34.8 GB on the same three Groth16 proofs, up to a
700M-cycle block, measured on the runner. Small proofs took the same
time.
- The prover docs name 6.9.0 and the GPU server. The SP1 prover guide's
troubleshooting section now covers that startup error and the
out-of-memory case, replacing SP1 5's Docker GPU error, which can no
longer happen.

The stateless-validator guests are a separate workspace that follows
ere's SP1, and are unaffected.

Upgrading an existing deployment to this release, with or without
downtime, is documented in lambdaclass#7417, which is stacked on this one and also
adds an SP1 version upgrade test. It relies on lambdaclass#7412 for deployments
with their own verifier, and on lambdaclass#7411 when the new release commits
batches another build sealed.

CI does not build or run the SP1 guests, so they were checked
separately:
- The L1 variant of the guest takes the spec's SSZ stateless input.
Built with SP1 6.9.0, it was executed over every stateless input of the
`tests-zkevm@v21.0.1` engine fixtures (33,637 blocks) and the 25
`tests-zkevm-benchmark@v21.0.5` workloads (up to 200M gas). Every public
output matches the fixture's expected output, including the 1,029 blocks
that exercise the EIP-2537 precompiles.
- Upgrades from v25.0.0 and v29.0.1 to this release were run on
`l2-gpu`, with and without downtime, for a deployment with its own
verifier (with lambdaclass#7412) and for one on a local `SP1VerifierGateway` whose
`v5.0.0` route was frozen (with lambdaclass#7411). In every case the new release's
SP1 6 proofs were verified on L1 and the L2 integration tests passed.
- The L2 guest was proven end to end on an NVIDIA RTX 4000 Ada (20 GB)
with the `gpu` feature, following the GPU integration workflow: a dev
L1, deployment with the v6.1.0 verifier, the sequencer and the SP1
prover. All 29 batches were proven with Groth16 and verified on L1, and
the L2 integration tests passed.

**Checklist**

- [ ] Updated `STORE_SCHEMA_VERSION` (crates/storage/lib.rs) if the PR
includes breaking changes to the `Store` requiring a re-sync.
MysticRyuujin pushed a commit to MysticRyuujin/ethrex that referenced this pull request Oct 16, 2026
)

**Motivation**

lambdaclass#7401 moves the L2 prover to SP1 6.9.0, and an existing deployment
cannot just switch provers: SP1 6 proofs need the `v6.1.0`
`SP1VerifierGroth16`, and what has to change on L1 depends on what the
OnChainProposer's `SP1_VERIFIER_ADDRESS` points at. A deployment with
its own verifier has to replace it (lambdaclass#7412). One on Succinct's
`SP1VerifierGateway` has nothing to replace, but the gateway froze its
`v5.0.0` route, so batches committed with SP1 5 and not verified in time
have to be reverted and committed again (lambdaclass#7411). The per-release upgrade
test only registers the new verification key, which covers neither
(lambdaclass#7415).

**Description**

- The deployment upgrade guide gets a "From v29 to v30" section for SP1
6:
- contract steps: deploy the `v6.1.0` verifier and the new
OnChainProposer implementation, switch both in one Timelock call,
`upgradeToAndCall(newImplementation, upgradeSP1Verifier(newVerifier))`,
and register the new verification key;
- moving the nodes without downtime (the `--admin.start-at` handover) or
with a stop, around the point where every old batch is verified and no
new proof has been sent;
- with Succinct's gateway: check that the batches to revert published no
withdrawals (lambdaclass#7414), `pause`, `revertBatch` and `unpause` through the
Timelock, register the key, and either revert the node's rollup store
(with the workarounds for lambdaclass#7413) or hand over to the new sequencer;
- what changes for the prover host, and that Aligned mode is
unavailable.
- The upgrades fundamentals explain what changes when the zero-downtime
handover moves to a new version: stop the old committer and let its
prover verify everything first, register the new key before the
handover, and take the old sequencer's enode from its startup log.
- A new SP1 version upgrade test runs the three cases on the GPU host
from an older release: own verifier, gateway with a stop, and gateway
without downtime. It shows how to deploy a gateway on the test L1 and
freeze its `v5.0.0` route, and records the results of the last run, from
v25.0.0.

This is the top of a stack: lambdaclass#7411, then lambdaclass#7412, then lambdaclass#7401, then this
one. The diff here is the docs only.

Closes lambdaclass#7415

**Checklist**

- [ ] Updated `STORE_SCHEMA_VERSION` (crates/storage/lib.rs) if the PR
includes breaking changes to the `Store` requiring a re-sync.

---------

Co-authored-by: Javier Chatruc <jrchatruc@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L2 Rollup client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants