Skip to content

Commit c424e53

Browse files
committed
update docs
1 parent 204843b commit c424e53

9 files changed

Lines changed: 195 additions & 98 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Follow the structured testing guide in [`docs/testing/openvm-upgrade-testing-gui
2525
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:
2626

2727
- **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.
29-
- **Quick Start**: [`scripts/shadow-testing/QUICKSTART.md`](scripts/shadow-testing/QUICKSTART.md)
30-
- **Automation**: [`scripts/shadow-testing/setup.sh`](scripts/shadow-testing/setup.sh) — one-command setup for postgres, coordinator, and prover.
28+
- **Docs**: [`tests/shadow-testing/docs/README.md`](tests/shadow-testing/docs/README.md) — full setup guide, troubleshooting, config reference.
29+
- **Quick Start**: [`tests/shadow-testing/docs/QUICKSTART.md`](tests/shadow-testing/docs/QUICKSTART.md)
30+
- **Automation**: [`tests/shadow-testing/scripts/setup.sh`](tests/shadow-testing/scripts/setup.sh) — one-command setup for postgres, coordinator, and prover.
3131

3232
Key hard-won rules:
3333
- **L2 RPC for coordinator task generation** (must support `debug_executionWitness`):
@@ -153,5 +153,5 @@ make coordinator_setup
153153
| [`docs/prover-coordinator-overview.md`](docs/prover-coordinator-overview.md) | Architecture, data flow, component relationships, common operations |
154154
| [`docs/testing/openvm-upgrade-testing-guide.md`](docs/testing/openvm-upgrade-testing-guide.md) | Step-by-step testing checklist after OpenVM / zkvm-prover upgrades |
155155
| [`docs/testing/docker-compose-e2e-guide.md`](docs/testing/docker-compose-e2e-guide.md) | Production-like E2E testing with Docker Compose + Coordinator Proxy |
156-
| [`docs/shadow-testing/README.md`](docs/shadow-testing/README.md) | Shadow coordinator + local prover setup for production task replay |
156+
| [`tests/shadow-testing/docs/README.md`](tests/shadow-testing/docs/README.md) | Shadow coordinator + local prover setup for production task replay |
157157
| [`docs/testing_reports/openvm-v1.6.0-guest-v0.8.0-May19.md`](docs/testing_reports/openvm-v1.6.0-guest-v0.8.0-May19.md) | Test report for PR #1783 (OpenVM 1.6.0, guest v0.8.0) |

crates/libzkp/src/proofs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,4 @@ mod tests {
339339
Ok(())
340340
}
341341
}
342+

rollup/internal/controller/sender/estimategas.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ func (s *Sender) estimateBlobGas(to *common.Address, data []byte, sidecar *types
102102
}
103103

104104
func (s *Sender) estimateGasLimit(to *common.Address, data []byte, sidecar *types.BlobTxSidecar, gasPrice, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int) (uint64, *types.AccessList, error) {
105+
// In dry-run mode, skip gas estimation and use a fixed gas limit.
106+
if s.config.DryRun {
107+
return 10000000, nil, nil
108+
}
109+
105110
msg := ethereum.CallMsg{
106111
From: s.transactionSigner.GetAddr(),
107112
To: to,
Lines changed: 1 addition & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,3 @@
11
# Quick Start: Shadow Coordinator + Prover
22

3-
For full details, see `docs/shadow-testing/README.md`.
4-
5-
## Prerequisites
6-
7-
1. **IDC port-forward active**: Mainnet RDS on `localhost:15432`
8-
2. **Docker installed** with GPU support (for prover)
9-
3. **Verifier assets** at `/tmp/shadow-verifier-assets/` (feynman, galileo, galileoV2)
10-
4. **SRS params** at `~/.openvm/params/` (kzg_bn254_22.srs, kzg_bn254_23.srs, kzg_bn254_24.srs)
11-
12-
## One-Command Setup
13-
14-
```bash
15-
cd scripts/shadow-testing
16-
17-
# Step 1: Start PostgreSQL
18-
./setup.sh --postgres
19-
20-
# Step 2: Import production tasks (requires RDS port-forward)
21-
./import-production-data.sh
22-
23-
# Step 3: Fetch L2 block headers
24-
python3 fetch-l2-blocks.py \
25-
--rpc https://mainnet-rpc.scroll.io \
26-
--db "postgresql://<user>:<password>@localhost:5433/shadow_rollup" \
27-
--start-block 33750000 --end-block 33770000
28-
29-
# Step 4: Link blocks to chunks
30-
psql "postgresql://<user>:<password>@localhost:5433/shadow_rollup" -c "
31-
UPDATE l2_block lb SET chunk_hash = c.hash
32-
FROM chunk c
33-
WHERE lb.number >= c.start_block_number AND lb.number <= c.end_block_number;
34-
"
35-
36-
# Step 5: Start coordinator (takes 2-3 min)
37-
./setup.sh --coordinator
38-
39-
# Step 6: Start prover (in another terminal)
40-
./setup.sh --prover
41-
```
42-
43-
## Monitoring
44-
45-
```bash
46-
# Check everything is running
47-
./setup.sh --status
48-
49-
# Watch coordinator logs
50-
docker logs -f shadow-coordinator-api-test --tail 100
51-
52-
# Watch prover logs (if using docker)
53-
docker logs -f shadow-prover --tail 100
54-
55-
# Check task assignment
56-
psql "postgresql://<user>:<password>@localhost:5433/shadow_rollup" -c "
57-
SELECT proving_status, COUNT(*) FROM chunk GROUP BY proving_status;
58-
"
59-
```
60-
61-
## Stop Everything
62-
63-
```bash
64-
./setup.sh --stop
65-
```
66-
67-
## Key Configuration Files
68-
69-
| File | Purpose |
70-
|------|---------|
71-
| `configs/shadow-coordinator-config.json` | Coordinator config template |
72-
| `configs/prover-local.json` | Prover config template |
73-
| `/tmp/shadow-coordinator-config.json` | Generated coordinator config (with L2 RPC) |
74-
| `/tmp/prover-local.json` | Generated prover config |
75-
76-
## Environment Variables
77-
78-
| Variable | Default | Description |
79-
|----------|---------|-------------|
80-
| `PROD_DB` | `postgresql://...localhost:15432/rollup` | Production RDS connection |
81-
| `SHADOW_DB` | `postgresql://...localhost:5433/shadow_rollup` | Shadow DB connection |
82-
| `VERIFIER_DIR` | `/tmp/shadow-verifier-assets` | Verifier asset path |
83-
| `IMAGE_TAG` | `v4.7.13-openvm16` | Docker image tag |
84-
| `L2_RPC` | `https://mainnet-rpc.scroll.io` | L2 RPC endpoint |
3+
> **Moved**: This document has been moved to [`tests/shadow-testing/docs/QUICKSTART.md`](../../tests/shadow-testing/docs/QUICKSTART.md).

tests/shadow-testing/README.md

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -384,19 +384,70 @@ The single biggest time sink was using the **wrong selector** for `finalizeBundl
384384
cast sig "finalizeBundlePostEuclidV2(bytes,uint256,bytes32,bytes32,bytes)"
385385
```
386386
387-
### Trap #2: Verifier Digest Mismatch
387+
### Trap #2: Verifier Mismatch — Digests *and* Plonk Verifier
388388
389-
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:
390390
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.
393+
394+
**Mainnet's registered verifier** (`0x0dE180…`) has:
395+
- Digests: `0x009160…` / `0x009305…`
396+
- Plonk verifier: `0x749fC77A…` (37KB legacy bytecode)
397+
398+
**Our locally-generated proofs** (guest v0.8.0) require:
399+
- 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:
407+
408+
```python
409+
import base64, json
410+
411+
# proof_json from coordinator/bundle table
412+
instances = base64.b64decode(proof_json['proof']['instances'])
413+
print(f"instances length: {len(instances)} bytes") # e.g. 1472
414+
415+
# For ZkEvmVerifierPostEuclid (new guest v0.8.0+):
416+
# 1472 bytes = 12 accumulators (384) + 2 digests (64) + 32 publicInputHash bytes (1024)
417+
# = 46 × 32-byte Fr elements
418+
digest1 = '0x' + instances[384:416].hex()
419+
digest2 = '0x' + instances[416:448].hex()
420+
```
421+
422+
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):
392429
393-
**Fix**: Deploy a new `ZkEvmVerifierPostEuclid` (not `PostFeynman`!) with matching digests:
394430
```bash
395-
# In scroll-contracts/
396-
cast send <DEPLOYER> "createZkEvmVerifierPostEuclid(address,bytes32,bytes32)" \
397-
<PLONK_VERIFIER> <DIGEST1> <DIGEST2> --rpc-url <ANVIL_RPC>
431+
# From tests/shadow-testing/
432+
./scripts/02-deploy-verifier.sh --config configs/mainnet.json
398433
```
399-
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`.
439+
4. Calls `MultipleVersionRollupVerifier.updateVerifier(10, startBatch, newWrapper)`.
440+
5. Updates `configs/mainnet.json` → `contracts.deployed_verifier`.
441+
442+
**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.
400451
401452
### Trap #3: `committedBatches` Is Sparse (EuclidV2)
402453
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Shadow Testing Lessons Learned
2+
3+
## 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...`).
10+
5. Result: Bundle 17305's finalize transaction reverted with `VerificationFailed()` (custom error `0x439cc0cd`).
11+
12+
### Root Cause
13+
- 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).
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Quick Start: Shadow Coordinator + Prover
2+
3+
For full details, see `tests/shadow-testing/docs/README.md`.
4+
5+
## Prerequisites
6+
7+
1. **IDC port-forward active**: Mainnet RDS on `localhost:15432`
8+
2. **Docker installed** with GPU support (for prover)
9+
3. **Verifier assets** at `/tmp/shadow-verifier-assets/` (feynman, galileo, galileoV2)
10+
4. **SRS params** at `~/.openvm/params/` (kzg_bn254_22.srs, kzg_bn254_23.srs, kzg_bn254_24.srs)
11+
12+
## One-Command Setup
13+
14+
```bash
15+
cd tests/shadow-testing
16+
17+
# Step 1: Start PostgreSQL
18+
./scripts/setup.sh --postgres
19+
20+
# Step 2: Import production tasks (requires RDS port-forward)
21+
./scripts/import-production-data.sh
22+
23+
# Step 3: Fetch L2 block headers
24+
python3 ./scripts/fetch-l2-blocks.py \
25+
--rpc https://mainnet-rpc.scroll.io \
26+
--db "postgresql://<user>:<password>@localhost:5433/shadow_rollup" \
27+
--start-block 33750000 --end-block 33770000
28+
29+
# Step 4: Link blocks to chunks
30+
psql "postgresql://<user>:<password>@localhost:5433/shadow_rollup" -c "
31+
UPDATE l2_block lb SET chunk_hash = c.hash
32+
FROM chunk c
33+
WHERE lb.number >= c.start_block_number AND lb.number <= c.end_block_number;
34+
"
35+
36+
# Step 5: Start coordinator (takes 2-3 min)
37+
./scripts/setup.sh --coordinator
38+
39+
# Step 6: Start prover (in another terminal)
40+
./scripts/setup.sh --prover
41+
```
42+
43+
## Monitoring
44+
45+
```bash
46+
# Check everything is running
47+
./scripts/setup.sh --status
48+
49+
# Watch coordinator logs
50+
docker logs -f shadow-coordinator-api-test --tail 100
51+
52+
# Watch prover logs (if using docker)
53+
docker logs -f shadow-prover --tail 100
54+
55+
# Check task assignment
56+
psql "postgresql://<user>:<password>@localhost:5433/shadow_rollup" -c "
57+
SELECT proving_status, COUNT(*) FROM chunk GROUP BY proving_status;
58+
"
59+
```
60+
61+
## Stop Everything
62+
63+
```bash
64+
./scripts/setup.sh --stop
65+
```
66+
67+
## Key Configuration Files
68+
69+
| File | Purpose |
70+
|------|---------|
71+
| `configs/shadow-coordinator-config.json` | Coordinator config template |
72+
| `configs/prover-local.json` | Prover config template |
73+
| `/tmp/shadow-coordinator-config.json` | Generated coordinator config (with L2 RPC) |
74+
| `/tmp/prover-local.json` | Generated prover config |
75+
76+
## Environment Variables
77+
78+
| Variable | Default | Description |
79+
|----------|---------|-------------|
80+
| `PROD_DB` | `postgresql://...localhost:15432/rollup` | Production RDS connection |
81+
| `SHADOW_DB` | `postgresql://...localhost:5433/shadow_rollup` | Shadow DB connection |
82+
| `VERIFIER_DIR` | `/tmp/shadow-verifier-assets` | Verifier asset path |
83+
| `IMAGE_TAG` | `v4.7.13-openvm16` | Docker image tag |
84+
| `L2_RPC` | `https://mainnet-rpc.scroll.io` | L2 RPC endpoint |
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ If you just want to get running, use the provided script:
4343

4444
```bash
4545
# 1. Set up shadow PostgreSQL
46-
cd scripts/shadow-testing
46+
cd tests/shadow-testing
4747
./setup.sh --postgres
4848

4949
# 2. Import production task data (requires RDS port-forward)
@@ -120,7 +120,7 @@ Export the latest N batches + their chunks + bundles from production RDS and imp
120120

121121
```bash
122122
# Edit these variables as needed
123-
# Credentials loaded from .env (see scripts/shadow-testing/.env.example)
123+
# Credentials loaded from .env (see tests/shadow-testing/.env.example)
124124
PROD_DB="postgresql://${PROD_DB_USER}:${PROD_DB_PASSWORD}@${PROD_DB_HOST}:${PROD_DB_PORT}/${PROD_DB_NAME}"
125125
SHADOW_DB="postgresql://${SHADOW_DB_USER}:${SHADOW_DB_PASSWORD}@${SHADOW_DB_HOST}:${SHADOW_DB_PORT}/${SHADOW_DB_NAME}"
126126
BATCH_LIMIT=50
@@ -174,7 +174,7 @@ The coordinator needs `l2_block` records to format chunk tasks (for block hashes
174174
Use the provided Python script or fetch blocks via L2 RPC:
175175

176176
```bash
177-
python3 scripts/shadow-testing/fetch-l2-blocks.py \
177+
python3 tests/shadow-testing/scripts/fetch-l2-blocks.py \
178178
--rpc https://mainnet-rpc.scroll.io \
179179
--db "postgresql://$SHADOW_DB_USER:$SHADOW_DB_PASSWORD@$SHADOW_DB_HOST:$SHADOW_DB_PORT/$SHADOW_DB_NAME" \
180180
--start-block 26000000 \
@@ -396,7 +396,7 @@ When `"dry_run": true` is set in the sender config:
396396
cd rollup && go build -o rollup_relayer ./cmd/rollup_relayer/app
397397
```
398398

399-
2. Configure `dry_run: true` in the sender config (see `scripts/shadow-testing/configs/rollup-relayer-dryrun.json`)
399+
2. Configure `dry_run: true` in the sender config (see `tests/shadow-testing/configs/rollup-relayer-dryrun.json`)
400400

401401
3. Start the relayer:
402402
```bash
File renamed without changes.

0 commit comments

Comments
 (0)