You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,9 +25,9 @@ Follow the structured testing guide in [`docs/testing/openvm-upgrade-testing-gui
25
25
For testing proof generation against **real mainnet production tasks** without interfering with the live system, use the **Shadow Coordinator** approach. This is significantly faster than a full shadow fork:
26
26
27
27
-**Architecture**: Local coordinator (`:8390`) + local prover (GPU), fed by imported production task data.
28
-
-**Docs**: [`docs/shadow-testing/README.md`](docs/shadow-testing/README.md) — full setup guide, troubleshooting, config reference.
-**Automation**: [`tests/shadow-testing/scripts/setup.sh`](tests/shadow-testing/scripts/setup.sh) — one-command setup for postgres, coordinator, and prover.
31
31
32
32
Key hard-won rules:
33
33
-**L2 RPC for coordinator task generation** (must support `debug_executionWitness`):
@@ -153,5 +153,5 @@ make coordinator_setup
153
153
|[`docs/prover-coordinator-overview.md`](docs/prover-coordinator-overview.md)| Architecture, data flow, component relationships, common operations |
154
154
|[`docs/testing/openvm-upgrade-testing-guide.md`](docs/testing/openvm-upgrade-testing-guide.md)| Step-by-step testing checklist after OpenVM / zkvm-prover upgrades |
Mainnet's registered verifier (`0x0dE180…`) has digests `0x009160…` / `0x009305…`, but the proofs generated by our local prover (guest v0.8.0) require digests `0x00398b…` / `0x002178…`.
389
+
This is the most subtle trap. There are **two** independent verifier components:
390
390
391
-
**Symptom**: Direct verifier call returns `VerificationFailed()`.
391
+
1. **`ZkEvmVerifierPostEuclid` / `ZkEvmVerifierPostFeynman`** — the Solidity wrapper that assembles public inputs and calls the plonk verifier.
392
+
2. **The plonk verifier contract** — a ~18KB (new guest) or ~37KB (mainnet legacy) EVM bytecode that performs the actual SNARK verification.
- Digests: extracted from the proof's `instances` array (see below)
400
+
- Plonk verifier: `coordinator/build/bin/assets_v2/verifier.bin` (18KB new bytecode)
401
+
402
+
**Symptom**: `VerificationFailed(0x439cc0cd)` even when digests *appear* correct. This happens because the plonk verifier bytecode itself differs between mainnet (37KB) and the new guest release (18KB). The wrapper may pass the right digests, but the underlying plonk verifier rejects the proof format.
403
+
404
+
**How to determine the correct verifier for your proofs**
405
+
406
+
The proof's `instances` array tells you exactly which wrapper + plonk verifier you need:
If `len(instances) == 1472` (or more generally `12+2+32 = 46` words), your proof is for **`ZkEvmVerifierPostEuclid`**. The wrapper computes `keccak256(publicInput)` and feeds each of the 32 hash bytes as a separate field element to the plonk verifier.
423
+
424
+
If `len(instances)` were smaller (e.g. 12+13 = 25 words), the proof would be for `ZkEvmVerifierPostFeynman`, which decomposes public inputs into 13 field elements directly.
425
+
426
+
**Fix — Automated deployment**
427
+
428
+
A script handles all three steps (deploy plonk, deploy wrapper, register):
392
429
393
-
**Fix**: Deploy a new `ZkEvmVerifierPostEuclid` (not `PostFeynman`!) with matching digests:
Then register it via `MultipleVersionRollupVerifier.updateVerifier(10, <START_BATCH>, <NEW_VERIFIER>)`.
434
+
435
+
What it does:
436
+
1. Reads `coordinator/build/bin/assets_v2/verifier.bin` and deploys it as the new plonk verifier via `cast send --create`.
437
+
2. Queries the shadow DB for the bundle proof, base64-decodes `instances`, and extracts `digest1`/`digest2` from offsets `[384:416]` and `[416:448]`.
438
+
3. Compiles and deploys `ZkEvmVerifierPostFeynman` (not `PostEuclid`!) with `forge create` using the new plonk address + extracted digests + `protocolVersion = 10`.
**Why `ZkEvmVerifierPostFeynman` and not `PostEuclid`**
443
+
444
+
`PostFeynman` computes `keccak256(abi.encodePacked(protocolVersion, publicInput))`, where `protocolVersion = (domain << 6) + stf_version` = `(0 << 6) + 10 = 10` for Scroll domain + V10 STF version. This matches exactly how the prover computes `bundle_pi_hash` in `pi_hash_versioned()`.
445
+
446
+
`PostEuclid` computes `keccak256(publicInput)` *without* the `protocolVersion` prefix, so the hash never matches the proof's embedded `bundle_pi_hash`. The 32-byte `protocolVersion` padding is visible in the proof's `instances` array at offset `[448:1472]` (32 words, each containing one byte of the hash).
447
+
448
+
**Why you cannot reuse the mainnet plonk verifier**
449
+
450
+
Mainnet's plonk verifier (`0x749fC77A…`, 37KB) was compiled for the old Halo2 circuit. The new guest v0.8.0 uses a different Halo2 outer circuit with a smaller verification key. The 18KB `verifier.bin` from `assets_v2/` is the *only* plonk verifier that understands the new proof format. Copying mainnet bytecode via `anvil_setCode` only works if you are verifying **mainnet-generated proofs**; it will always fail for**locally-generated proofs** from a newer guest.
400
451
401
452
### Trap #3: `committedBatches` Is Sparse (EuclidV2)
## 2026-06-03: Production data import overwrote locally-valid shadow proofs
4
+
5
+
### What Happened
6
+
1. Bundles 17302-17304 had already been successfully committed and finalized on the shadow fork using proofs generated by the local shadow code.
7
+
2. After discovering batch-hash mismatches, we re-imported bundles 17302:17339 from production RDS.
8
+
3.**Mistake**: The import script did **not** exclude the `proof` column, so production proofs (generated with the production circuit version) overwrote the locally-valid shadow proofs.
9
+
4. The production proof verifier digests (`0x0091609a...` / `0x009305f0...`) did **not** match the shadow verifier digests (`0x00398b78...` / `0x0021785a...`).
- The production and shadow test environments were running different zkvm-prover / circuit versions.
14
+
- Proofs are tightly coupled to circuit versions: each proof embeds verifier digests that must exactly match the verifier contract deployed on-chain.
15
+
- The data import blindly copied entire tables without protecting the shadow-local proofs.
16
+
17
+
### How to Prevent This
18
+
1.**Always exclude `proof` columns when importing production data**. Correct approach:
19
+
- Import raw data (headers, transactions, state roots, etc.) for batches, chunks, and L2 blocks.
20
+
-**Do NOT import `bundle.proof`, `batch.proof`, or `chunk.proof`.**
21
+
- After import, reset `proving_status` to `ProvingTaskUnassigned` so the coordinator re-schedules proving tasks.
22
+
23
+
2.**Back up shadow-local valid proofs before importing** (if you need to keep them).
24
+
25
+
3.**Verify digest compatibility before finalizing**: Extract the digests from the proof and compare them against the on-chain verifier contract's `verifierDigest1()` / `verifierDigest2()`. Only send the finalize transaction after confirming they match.
26
+
27
+
### Recovery Steps
28
+
1. Clear the `proof` field for bundles 17305+.
29
+
2. Reset `proving_status` and `batch_proofs_status` to pending.
30
+
3. Reset proving status for the corresponding batches and chunks as well.
31
+
4. Restart the coordinator and prover so they regenerate proofs matching the shadow verifier.
32
+
33
+
### Pre-Import Checklist
34
+
-[ ] Confirm the import script excludes `proof` columns.
35
+
-[ ] Confirm `proving_status` is reset to `ProvingTaskUnassigned` for bundles/batches/chunks after import.
36
+
-[ ] Confirm shadow verifier digests match the expected circuit version.
37
+
-[ ] Confirm prover and coordinator can communicate (JWT tokens are valid).
0 commit comments