Skip to content

feat(scenarios): cdk-erigon FEP to PP rollback scenario - #268

Open
jhkimqd wants to merge 7 commits into
mainfrom
feat/cdk-erigon-fep-to-pp-rollback-scenario
Open

jhkimqd wants to merge 7 commits into
mainfrom
feat/cdk-erigon-fep-to-pp-rollback-scenario

Conversation

@jhkimqd

@jhkimqd jhkimqd commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Why

Silicon (mainnet rollup ID 10, chain 2355, cdk-erigon v2.61.20 fork 12 validium, cdk-node 0.5.4-rc1) can no longer verify batches: agglayer 0.6.0 disabled interop_sendTx (-10009), the only settlement path of the legacy cdk-node aggregator. On 2026-09-17 it had lastBatchSequenced 76058 vs lastVerifiedBatch 75940, and the cdk-erigon FEP to PP runbook cannot start because AgglayerManager.initMigration reverts with AllSequencedMustBeVerified() while the counters differ. Agglayer 0.5.x is sunset, so the gap cannot be closed by verifying.

What

scenarios/cdk-erigon-fep-to-pp-rollback/ reproduces that situation on kurtosis-cdk with Silicon's versions and validates the way out end to end:

  1. deposit L1 to L2, withdraw L2 to L1, legacy aggregator verifies through agglayer 0.6.0-rc.5, claim on L1
  2. Polygon upgrades agglayer to 0.6.0: cdk-node logs -10009, sequencing continues, a withdrawal lands in a sequenced-but-unverified batch
  3. initMigration reverts with AllSequencedMustBeVerified()
  4. operator stops sequencing and calls rollbackBatches(rollup, lastVerifiedBatch): counters equal, both RollbackBatches events, L2 block hashes unchanged, erigon's zkevm_virtualBatchNumber follows on both nodes
  5. cdk-erigon to v2.61.24 with the runbook's PP config (sequencer re-added with its persistent datadir), legacy components stopped
  6. aggkit 0.8.1 aggsender in dry run, initMigration to AggchainECDSAMultisig, bootstrap certificate up to the last verified block, normal certificates, the in-window withdrawal claimed on L1

Around every one of those steps both cdk-erigon nodes are probed (erigon_health: block number, batch/virtual/verified counters, fork id, reference block hash, zkevm_isBlockVirtualized, zkevm_getBatchByNumber, eth_getLogs, debug_traceTransaction, eth_call) and made to do real work: transactions submitted through the RPC node and the sequencer, L1 to L2 deposits claimed on L2 after the rollback and after the migration.

RUNBOOK.md is the operator runbook for Silicon. Every observation block is rendered from the committed evidence (scripts/render-runbook-observations.py), which is the unmodified CI artifact of run 35286572861: command outputs, state snapshots, service logs, health probes and Playwright screenshots of the Kurtosis Enclave Manager, with key material redacted while recording.

Findings worth reading even if you skip the rest

  • What rollbackBatches does to cdk-erigon (runbook section 3b, observed on both nodes at nine stages): once the RollbackBatches block is final, the L1Syncer stage deletes the local L1 sequence records, zkevm_virtualBatchNumber drops to the target, zkevm_isBlockVirtualized flips to false for the rolled-back range and zkevm_getBatchByNumber loses its sendSequencesTxHash. Block hashes, local batch numbers, eth_getLogs, debug_traceTransaction, transaction submission through either node, datastream sync and L1 to L2 deposits claimed on L2 are unaffected, before and after the migration. There is no L2 unwind. A read-only mainnet simulation from Silicon's admin succeeds today.
  • initMigration on mainnet is callable by the Agglayer admin, a 5-of-9 Gnosis Safe holding UPDATE_ROLLUP_ROLE without timelock; the timelock path would add 3 days. The runbook makes the actor and the signing lead time explicit.
  • aggkit 0.8.1 defaults to Mode = "Auto", which reads the consensus type from the rollup contract and exits on a legacy PolygonValidiumEtrog. Starting aggkit in sync-only mode before the migration needs Mode = "PessimisticProof".
  • When the target rollup type was added on L1 before the cdk-erigon upgrade (Silicon: mainnet type 14), the v2.61.24 sequencer logs received UpdateRollupTopic for unknown rollup type once, keeps producing and keeps fork 12; it must not be restarted before its next clean L1 Sequencer sync finished. RPC nodes do not run that stage and are unaffected.
  • kurtosis service update recreates containers without their persistent directories and dropped a mount; the scenario re-adds the sequencer and aggkit with Starlark that declares the same persistent_key.

Validation

  • The author's Apple Silicon host cannot run the fork 12 zkevm-prover (amd64/AVX2: SIGILL under Rosetta; under QEMU the x86 agglayer binary exceeds kurtosis-cdk's 180 s agglayer vkey task), so validation ran on ubuntu-latest through the new workflow, about 25 minutes per run.
  • Seven full runs passed with identical invariants: 35201843011, 35204567044, 35207232742, 35209534700, 35211930721, 35214396395 and, with the cdk-erigon health probes, 35286572861 (committed evidence). An eighth run on the final commit also passed: 35289107019.
  • An adversarial validator reviewed the scenario, runbook and evidence in two passes (contract source, cdk-erigon and aggkit source, mainnet reads, artifact comparison) and rated it ship-ready after the fixes now included.

🤖 Generated with Claude Code

@jhkimqd
jhkimqd force-pushed the feat/cdk-erigon-fep-to-pp-rollback-scenario branch 16 times, most recently from 9e920c6 to 00d3983 Compare September 17, 2026 11:10
jhkimqd and others added 3 commits September 17, 2026 20:11
Reproduce on kurtosis-cdk the state of a fork 12 validium whose cdk-node
aggregator can no longer settle after agglayer 0.6.0 disabled
interop_sendTx: batches sequenced but unverified, initMigration reverting
with AllSequencedMustBeVerified(). Validate the recovery: stop sequencing,
rollbackBatches to lastVerifiedBatch (no L2 unwind), cdk-erigon upgrade and
PP config, initMigration to AggchainECDSAMultisig, aggkit bootstrap and
normal certificates, and the in-window withdrawal claimable on L1.

Versions mirror Silicon mainnet (cdk-erigon v2.61.20, cdk 0.5.4-rc1,
agglayer-contracts v12.2.3, aggkit 0.8.1 from the runbook). Everything is
driven through the kurtosis CLI and Starlark: config changes go through
files artifacts; the sequencer and aggkit are (re)added with Starlark that
declares their persistent directories, because `kurtosis service update`
drops persistent directories and mounts. Screenshots of the Kurtosis
Enclave Manager are taken with Playwright by clicking through the UI, and
the runbook's observation blocks are rendered from the evidence files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Runs on pull requests touching the scenario code (docs and evidence are
excluded) and on manual dispatch. The fork 12 zkevm-prover image is
amd64/AVX2 only and cannot run on Apple Silicon (SIGILL under Rosetta, too
slow under QEMU for kurtosis-cdk's task timeout), so the scenario is
validated on an amd64 runner. Kurtosis 1.19.0 is installed from the release
artifacts because the apt repository lags. Evidence and Kurtosis Enclave
Manager screenshots are uploaded as an artifact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator runbook for a cdk-erigon chain whose batches are sequenced but no
longer verified after agglayer 0.6.0: read the state, stop sequencing, roll
the unverified batches back on L1, upgrade cdk-erigon, start aggkit, migrate
to AggchainECDSAMultisig, bootstrap and normal certificates, claims. Every
step quotes the outputs of GitHub Actions run 35211930721, whose unmodified
artifact (command outputs, state snapshots, logs, Enclave Manager
screenshots; key material redacted while recording) is committed under
evidence/validated-run-1. Four earlier runs passed with the same invariants.

Findings recorded for the standard runbook: initMigration requires
lastBatchSequenced == lastVerifiedBatch (rollbackBatches closes the gap)
and is callable by the Agglayer admin 5-of-9 Safe without the timelock;
aggkit 0.8.1 needs Mode = "PessimisticProof" before the migration;
cdk-erigon's sequencer logs "UpdateRollupTopic for unknown rollup type"
once when the rollup type predates its upgrade, continues, and must not be
restarted before its next clean L1 sync pass; RPC nodes are unaffected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jhkimqd
jhkimqd force-pushed the feat/cdk-erigon-fep-to-pp-rollback-scenario branch from 00d3983 to 2d7ed68 Compare September 17, 2026 11:11
@jhkimqd
jhkimqd marked this pull request as ready for review September 17, 2026 11:39
jhkimqd and others added 4 commits September 18, 2026 08:22
The aggkit team's main concern is how the L1 rollbackBatches call affects
cdk-erigon. Probe both nodes (sequencer and rpc) before the call, right
after it, after L1 finality and at every later stage: eth_* calls, block
hashes in the rolled-back range, eth_getLogs over it, debug_traceTransaction
(aggkit depends on it), the zkevm_* namespace incl. zkevm_isBlockVirtualized
and zkevm_getBatchByNumber metadata. Assert what must hold (both nodes
answer, fork 12, reference block hash intact, debug trace works) and record
the rest as JSON evidence.

Also exercise what cdk-erigon must keep doing: user transactions through
the rpc node (pool manager path, then the sequencer path once the pool
manager is stopped) and through the sequencer are mined; L1 to L2 deposits
made after the rollback and after the migration are claimed on L2, proving
global exit roots keep being injected; the rpc node stays in step with the
sequencer through the datastream. RUNBOOK.md gains section 3b, rendered
from the new evidence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Point the runbook and the committed evidence at run 35286572861, the first
run with the cdk-erigon health probes. Section 3b answers the aggkit team's
question with observed data from both nodes at every stage: once the
RollbackBatches block is final, zkevm_virtualBatchNumber drops to the
target, zkevm_isBlockVirtualized flips to false for the rolled-back range
and zkevm_getBatchByNumber loses its sendSequencesTxHash; block hashes,
local batches, eth_getLogs, debug_traceTransaction, transaction submission
through the rpc node and the sequencer, datastream sync and L1 to L2
deposits claimed on L2 are unaffected, before and after the migration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The rendered zkevm_getBatchByNumber comparison printed Python booleans
(True) next to JSON values (null, false); print every value as JSON.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reference block reads "not virtualized" at gap-open because it had
just been produced and was not yet sequenced, not because of the rollback.
Derive that sentence from the probe data instead of leaving readers to
work it out from the batch numbers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant