feat: new tool - exit_certificate#1582
Conversation
|
Claude finished @krlosMata's task in 4m 37s —— View job PR Review: exit_certificate Tool ✅🚀 What's NewA new standalone CLI tool ( 🐛 Bug FixesNone. 📋 Config UpdatesNew config file: Required fields:
Optional fields:
|
6d18df4 to
85a1fa9
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new standalone exit-certificate CLI tool under tools/exit_certificate/ to generate Agglayer exit certificates for aggchain migration by scanning L2/L1 state and producing an agglayer/types.Certificate with BridgeExit entries.
Changes:
- Implements a 6-step pipeline (0 → A → B → C → D → E) for LBT generation, address discovery, balance scanning, SC-locked computation, certificate building, and unclaimed L1→L2 deposit detection.
- Adds JSON-RPC batching/concurrency utilities plus step-wise resumable output persisted to an output directory.
- Adds unit/integration tests and documentation/config examples for running the tool.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/exit_certificate/worker.go | Generic worker-pool helper used to parallelize step workloads. |
| tools/exit_certificate/types.go | Shared data structures for step outputs and deposit/event models. |
| tools/exit_certificate/step_0.go | Step 0 implementation: generate LBT by scanning bridge logs + supplies. |
| tools/exit_certificate/step_a.go | Step A implementation: scan blocks/txs and trace touched addresses. |
| tools/exit_certificate/step_b.go | Step B implementation: EOA/contract classification and balance scanning. |
| tools/exit_certificate/step_c.go | Step C implementation: compute SC-locked values from LBT vs EOA totals. |
| tools/exit_certificate/step_d.go | Step D implementation: build the exit certificate BridgeExits. |
| tools/exit_certificate/step_e.go | Step E implementation: scan L1 BridgeEvents and add unclaimed deposits. |
| tools/exit_certificate/rpc.go | JSON-RPC client utilities: batch/single RPC, retries, concurrency batching. |
| tools/exit_certificate/hex.go | Hex/decimal parsing helpers and ABI safety conversions. |
| tools/exit_certificate/config.go | Config loading/validation, defaults, and LBT file parsing helpers. |
| tools/exit_certificate/run.go | CLI execution wiring: full pipeline + step-by-step resumability and I/O. |
| tools/exit_certificate/cmd/main.go | CLI binary entrypoint using urfave/cli. |
| tools/exit_certificate/README.md | Tool documentation: config, steps, usage, outputs, testing. |
| tools/exit_certificate/.gitignore | Ignore local parameters/output/binary artifacts for this tool. |
| tools/exit_certificate/parameters.json.example | Example standalone JSON config for running the tool. |
| tools/exit_certificate/step_a_test.go | Unit tests for hex block parsing helper(s) used in Step A. |
| tools/exit_certificate/step_b_test.go | Unit tests for hex-to-bigint helper used in Step B. |
| tools/exit_certificate/step_c_test.go | Unit tests for SC-locked computation behavior and edge cases. |
| tools/exit_certificate/step_d_test.go | Unit tests for certificate construction from EOA + SC-locked inputs. |
| tools/exit_certificate/step_e_test.go | Unit tests for BridgeEvent decoding and claimed-set filtering logic. |
| tools/exit_certificate/rpc_test.go | Unit tests for batch/single RPC, retry behavior, and error handling. |
| tools/exit_certificate/run_test.go | Unit tests for block parsing and JSON save/load helpers. |
| tools/exit_certificate/config_test.go | Unit tests for config parsing, defaults, and LBT parsing helpers. |
| tools/exit_certificate/integration_test.go | Integration-style tests for production-like config/data shapes (skippable). |
4936ffa to
63a43be
Compare
|
ac0936d to
2122813
Compare
|
- Add overflow checks for big.Int to uint32/uint64 conversions (safeUint32, safeUint8) - Add max metadata size validation (1MB) in decodeBridgeEvent to prevent DoS - Cap batch size to RPCBatchSize in fetchTotalSupplies - Return error from parseBlockNumber on invalid input instead of silent zero - Extract globalIndex magic numbers to named constants - Add progress logging to Step D - Document native token handling in step_c indexByAddress - Fix all golangci-lint issues (errcheck, gci, gosec, lll, mnd, prealloc, unparam) Made-with: Cursor
- Scan L2 bridge for ClaimEvent logs so Step E correctly identifies already-claimed deposits instead of treating all as unclaimed (joanestebanr) - Fail on trace/scan errors instead of warn+continue: traceTransactions, fetchL1BridgeEvents now propagate errors (partial scans are unsafe) - Fix encodeBalanceOf: use zero-padding (LeftPadBytes) instead of space-padding (%064s) which produced invalid hex calldata - Use strconv.ParseUint instead of fmt.Sscanf to reject trailing non-numeric input like "123abc" - Set MaxIdleConnsPerHost=100 instead of 0 (0 defaults to 2 in net/http) - Preserve OriginNetwork/OriginTokenAddress from LBT for native token entries (supports chains with custom gas tokens) - Add decodeClaimEvent tests Made-with: Cursor
- Extract magic number 32 to named constant abiWordSize in step_b.go - Pre-allocate claims slice in fetchClaimEventsInRange Made-with: Cursor
- Replace ClaimEvent log scanning with isClaimed(depositCount, 0) eth_call on L2 bridge contract (authoritative claimed bitmap) - Extract helper functions across all steps to bring every function under diffguard thresholds (complexity ≤ 10, size ≤ 50 lines) Made-with: Cursor
Add exit_certificate binary to the Makefile build-tools target so it builds alongside the other tools. Add maskRPCURL helper that strips the path from RPC URLs before logging, preventing API key exposure in error messages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndling and logging - Add L2StartBlock option to config so block scanning starts from a configurable block instead of always from block 0 - Add label parameter to concurrentBatchRPC to identify each call site in progress logs (e.g. "L2 RPC/blockHeaders", "L2 RPC/balanceOf") - Improve batchRPC: log individual RPC-level errors via log.Warn and return the first error instead of silently dropping failed responses; add response-count validation - Add detailed app.Description to the CLI listing all pipeline steps (0, A, B, C, D, E) and how to run individual steps - Add .PHONY declarations for build-tools targets in Makefile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…king, balance capping, and ignoreUnclaimed --step flag now supports range notation: "f-i" expands to f,g,h,i and "f-" expands to f through sign (submit/wait always require explicit opt-in). Step G reads InitialLocalExitRoot from the bridge contract before replaying exits (via Anvil fork, or against the real L2 RPC for the empty-exits case). A 1-minute sleep separates the last bridgeAsset call from the final getRoot read. Step H verifies that its agglayer settled LER matches the InitialLocalExitRoot reported by step G, returning a hard error on mismatch. Step I (single-step mode) now prefers step-f-capped-certificate.json over step-e-exit-certificate.json, consistent with step G. Step F balance capping is rewritten: buildCapMap + capBridgeExits are replaced by a single capCertificateExits function that processes exits in order, deducting each exit's amount from a per-token RemainingBalance (= min(LBT, agglayer)). Exits that exceed the remaining budget are capped to it; exits that arrive when the budget is exhausted are dropped. RemainingBalance is carried on TokenBalanceCheck (json:"-"). Step E gains options.ignoreUnclaimed: when true, unclaimed deposits are detected and logged as warnings but not added to the certificate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ging Apply crypto.Keccak256 to raw BridgeEvent metadata before assigning it to BridgeExit.Metadata in Step I, matching aggsender's convertBridgeMetadata behaviour. Without this, BridgeExit.Hash() produced a different value than aggsender, causing CertificateID and therefore the certificate hash to diverge — making the signature unverifiable by the agglayer. Also log signer address, certificate fields (networkID, height, LERs), CertificateID, and hash-to-sign in Step SIGN to aid future debugging. Add a pipeline step summary table to the README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…filtering
Add optional bridge service cross-check to Step E and split unclaimed
L1→L2 deposits by leaf type.
Changes:
- config: add `bridgeServiceURL` and `bridgeServiceType` options
("aggkit" or "zkevm") for configuring the bridge service endpoint
- step_e: separate unclaimed deposits into assets (leaf_type=0) and
messages (leaf_type=1); only assets are added to the certificate
- step_e: log a single info line with the message count instead of
per-deposit warnings
- step_e: log a single token-grouped summary for ignored unclaimed
assets (name and decimals fetched from contract; ETH shown in ETH)
- step_e: when bridgeServiceURL is set, compare the bridge service's
pending-bridges set against the L1 scan unclaimed set and error only
on discrepancies; supports both aggkit (/bridge/v1/bridges) and
zkevm-bridge-service (/pending-bridges) APIs
- run: always save `step-e-unclaimed-messages.json` (even when empty)
- rpc: add `httpGetJSON` helper for REST GET calls
- kurtosis script: auto-detect and configure zkevm-bridge-service-001
as the bridge service when available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rors
Per the function contract ("individual RPC errors are logged and become
nil entries"), per-item RPC errors must not bubble up as a Go error.
Remove the early-exit check on responses[0].Error and the firstRPCErr
accumulator — errors are already logged as warnings and the result slot
is left nil.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al Step E behavior Remove unused functions depositsToImportedExits and depositsToExits from step_e.go; these were never called and represented unimplemented logic (Merkle proof support for adding unclaimed L1→L2 deposits to the certificate). Remove unused ABI selector constants gasTokenAddressSelector and gasTokenNetworkSelector from step_0.go. Fix README.md and CLAUDE.md to reflect what Step E actually does: - When unclaimed asset deposits are found and ignoreUnclaimed=false → pipeline errors (Merkle proof support not yet implemented) - When ignoreUnclaimed=true → deposits are detected and logged, certificate unchanged - imported_bridge_exits is not populated by Step E today - Step I prefers step-f-capped-certificate.json over step-e-exit-certificate.json when it exists - Add missing abortOnGenesisBalance option to the options table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…factor fetch logic - Fix zkevmDeposit JSON tags and TotalCnt type to match actual API response - Fix dest_net query param and restrict cross-check to leaf_type=0 (assets) - Refactor bridge service fetchers to accept leafType and share comparison logic - Fix formatTokenAmount precision loss for small sub-unit values - Always persist unclaimed files even when Step E fails Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ross-check The bridge service check only covers leaf_type=0 (assets), so the comparison must use the asset subset rather than all unclaimed deposits. Also split by leaf type before the check so the log line can report asset/message counts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Explain why l1RpcUrl matters in practice, warn that exitAddress must be a key you control, document the signerConfig format, and add a table describing when to use continueOnTraceError, abortOnGenesisBalance and ignoreUnclaimed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion options Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
It is needed by Step E and Step I; without it the certificate is incomplete. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…EADME List l1RpcUrl, exitAddress and signerConfig as the fields that must be filled in before running the tool, and link to the main README for the full field reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… API Introduce options.agglayerAdminToken to pass an Authorization: Bearer header when calling admin_getTokenBalance in Step F. Required when agglayerAdminURL is protected by Google Cloud IAP. Also replaces the flat agglayerGrpcUrl string option with a structured agglayerClient config object (agglayer.ClientConfig), enabling TLS, timeout and retry customization for gRPC steps H, SUBMIT, and WAIT. Add ready-to-use config examples for zkevm-cardona and zkevm-mainnet in config-examples/. Documentation updated with IAP token instructions and environment-specific service account / audience values for spec, bali, cardona, and mainnet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tools/exit_certificate: fix lll, mnd, gci, whitespace, goconst, gocritic, makezero, unparam, prealloc, and errorlint issues; add named constants (abiWordBytes, ethDecimals, hexBase, etc.) to hex.go; remove unused params from fetchGasTokenInfo and checkNativeGasToken; expand unit test coverage with hex_test.go, step_g_test.go and additional cases in rpc_test.go, config_test.go, step_f_test.go - aggsender, bridgeservice, multidownloader, scripts, backward_forward_let: suppress gosec false-positives with nolint directives - db/migrations/testutils: add gosec nolint alongside existing mnd nolint - l1infotreesync/migrations: preallocate migrations slice - sync/evmdownloader_test: preallocate testCases slice Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds G204, G602, G703, G118 to the global gosec excludes in .golangci.yml so local and CI golangci-lint produce identical results regardless of version. Removes now-redundant //nolint:gosec directives from 6 files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
G703 and G118 are not valid rule IDs in gosec as bundled with golangci-lint v2.4.0 (used by CI). Move them from gosec.excludes (schema-validated) to exclusions.rules with text matching, which is version-agnostic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… for SC-locked exits (#1622) ## 🔄 Changes Summary ### Fix F-01: `ensureERC20Balance` storage patching for SC-locked ERC-20 exits - Implements `ensureERC20Balance` in Step G, which was a stub that always returned an error without actually patching Anvil storage. - The function now patches `_balances[account]` via `hardhat_setStorageAt` using a two-layout detection strategy, verifying `balanceOf` after each attempt: 1. **OZ v4 non-upgradeable**: `_balances` mapping at storage slot 0 2. **OZ v5 upgradeable**: `_balances` inside the namespaced `ERC20Storage` struct at `ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00` - Adds a package-level `erc20NamespacedStorageLocation` constant documenting the OZ v5 storage namespace derivation. ### Refactor: remove `lbtFile` config option - `lbtFile` was an escape hatch to skip Step 0 by providing a pre-generated LBT. Step 0 now always runs, so the field is removed. - Merges `RunStepCWithEntries` back into `RunStepC` (simpler API, no config dependency). - Updates `resolveOrGenerateLBT`, `loadWrappedTokensFromLBT`, `runSingleC`, `runSingleB`, `runSingleF`, `runSingleG` accordingly. ### Kurtosis script improvements - Adds two helper scripts for the Kurtosis test environment. - Embeds a pre-generated exit address keypair (`0xe25f5B65E4976025f670e52b790a9746F27A3DB6`) in `configuration_based_on_kurtosis.sh` so the exit address is stable across runs without requiring Foundry at script runtime. The private key and an encrypted keystore (password: `test`) are written to `tmp/` on first execution. ##⚠️ Breaking Changes - `lbtFile` config field removed. Any existing config files using it will have the field silently ignored (unknown JSON fields are not errors, but Step 0 will now always run). ## 📋 Config Updates - `lbtFile` removed from `Config` and `rawConfig`. Step 0 is no longer skippable via config. ## ✅ Testing - 🤖 **Automatic**: Existing unit tests pass (`ok github.com/agglayer/aggkit/tools/exit_certificate`) - 🖱️ **Manual**: 1. Run `tools/exit_certificate/scripts/reproduce_sc_locked.sh` against a live Kurtosis `aggkit` enclave 2. The script deploys a `TokenHolder` smart contract on L2, transfers wrapped ERC-20 tokens to it, then drives the exit-certificate tool from steps 0→G 3. Before fix: Step G failed with `ERC20InsufficientBalance` (`0xe450d38c`) when replaying the SC-locked `BridgeExit` 4. After fix: Step G completes and emits a valid `NewLocalExitRoot` ## 🐞 Issues - Fixes: #1624 ## 🔗 Related PRs - N/A ## 📝 Notes - `TokenWrapped` (wTTK) deployed by `AgglayerBridge` uses OZ v5 upgradeable storage, so the slot-0 attempt is a no-op. The second candidate (namespaced storage) is the one that matches and patches the balance correctly. - The two-layout approach is safe: a failed slot write leaves the token balance unchanged and the loop moves to the next candidate. If neither layout works the function returns a descriptive error. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…runs (#1627) ## 🔄 Changes Summary - Changed `cfg.TargetBlock` type from `string` to `aggkittypes.BlockNumberFinality`, enabling finality keywords (`LatestBlock`, `FinalizedBlock`, `SafeBlock`, `PendingBlock`), decimal/hex block numbers, and offset notation (e.g. `LatestBlock/-10`). - Added `Step0Result.TargetBlock uint64` — the concrete resolved block number is now part of the Step 0 output and persisted to `step-0-l2_target_block.json` (new file). - Removed `ResolvedTargetBlock` from `Config`; the resolved block number is passed as an explicit `targetBlock uint64` parameter to `RunStepA`, `RunStepB`, and `RunStepG`. - Single-step runners (`runSingleA/B/G`) load the block from `step-0-l2_target_block.json` via a new `loadTargetBlock` helper. - Updated README with target-block resolution table and updated Step 0 output list. - Added output-directory cleanup hint at the end of the kurtosis configuration script. ##⚠️ Breaking Changes - 🛠️ **Config — `targetBlock` values renamed**: existing `parameters.json` files must update their `targetBlock` field to use the new PascalCase keywords: - `"latest"` → `"LatestBlock"` - `"finalized"` → `"FinalizedBlock"` - `"safe"` → `"SafeBlock"` - `"pending"` → `"PendingBlock"` The default (empty string) still resolves to `LatestBlock`. Decimal and hex block numbers are unchanged. - 🗑️ **New output file**: `step-0-l2_target_block.json` is a new file produced by Step 0. Note: `step-0-result.json` (from a previous PR) was already renamed to `step-0-lbt.json`; any tooling still reading `step-0-result.json` must be updated to `step-0-lbt.json`. ## 📋 Config Updates ```json // Before "targetBlock": "latest" // After "targetBlock": "LatestBlock" // or "FinalizedBlock", "SafeBlock", "PendingBlock" "targetBlock": "LatestBlock/-10" // latest minus 10 blocks "targetBlock": "12345678" // decimal (unchanged) "targetBlock": "0xBCDE34" // hex (unchanged) ``` ## ✅ Testing - 🖱️ **Manual**: Run full pipeline with `targetBlock: "LatestBlock"` and verify `step-0-l2_target_block.json` is written with a valid block number; subsequent single-step runs for A, B, and G should pick it up automatically. ## 🐞 Issues - Closes #1625 ## 🔗 Related PRs - Base branch: `feat/exit_certificate_f01_token_sclocked` --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## 🔄 Changes Summary
- Propagate trace errors through the worker pool and abort all in-flight
workers on first failure when `ContinueOnTraceError=false` (F-09)
- Add `StepAWindowSize` config option to tune Step A chunk size
independently from `blockRange`
- Replace `[]common.Hash failedTraces` with `[]FailedTrace{Hash, Error}`
so callers get the RPC error alongside the hash
- Promote `fetchSetSovereignTokenEvents` /
`applySovereignTokenOverrides` errors from silent `log.Warn` to returned
errors in `RunStep0`
- Fix variable shadowing bug for `nativeEntry` in `RunStep0` (`:=` →
`=`)
- Remove dead `lbtFile` config field and merge `RunStepCWithEntries`
back into `RunStepC`
## ⚠️ Breaking Changes
- 🛠️ **Config**: `lbtFile` option removed from `Options` — it was an
escape hatch to skip Step 0 that is no longer needed since Step 0 always
runs
- 🛠️ **Config**: `blockRange` no longer controls the block window size
in Step A — `stepAWindowSize` is now used exclusively for that (defaults
to 5000). Existing configs that relied on `blockRange` to tune Step A
throughput should add an explicit `stepAWindowSize`
- 🔌 **API/CLI**: `runWorkerPool`, `startWorkers`, `collectResults` now
require a `context.Context` as first argument
## 📋 Config Updates
- 🧾 New optional field `stepAWindowSize` (default: 5000):
```json
"options": {
"blockRange": 10000,
"stepAWindowSize": 10000
}
```
## ✅ Testing
- 🤖 **Automatic**:
- Unit tests for `traceOneTransaction` (success, dedup, RPC error, bad
JSON, null+error)
- Unit tests for `traceTransactions` (continueOnError path,
abort-on-error path)
- End-to-end `TestRunStepA_AbortOnTraceError` against a fake HTTP server
verifying context cancellation stops the pool
- 🖱️ **Manual**: Run Step A with a node that returns trace errors with
`continueOnTraceError=false` and verify the tool exits immediately with
the offending hash and error message
## 🐞 Issues
- Closes agglayer/pm#346
- Partially fixes agglayer/pm#349 (`Problem 2 — Variable shadowing makes
nil-check dead code`)
## 🔗 Related PRs
- Base: feat/exit_certificate_f05_target_block (block finality
resolution for Step 0)
## 📝 Notes
- `StepAWindowSize` exists because `debug_traceTransaction` RPC calls
are more expensive than `eth_getLogs`; operators may want a smaller
window for Step A without changing the log-query range used by Steps 0,
B, and E
- Context cancellation in `collectResults` drains `resultCh` in a
background goroutine to let workers release resources cleanly instead of
blocking
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… rollup diagnostics (#1630) ## 🔄 Changes Summary - **Step A → A1 + A2 split**: Step A is now two sub-steps. A1 runs `debug_traceTransaction` (prestateTracer + diffMode) as before and records failed hashes. A2 recovers addresses from `eth_getTransactionReceipt` for each A1 failure, extracting from/to/contractAddress/log emitters. The combined `step-a-addresses.json` is the union of both. - **Step aliases**: `--step a` expands to `a1,a2`; both sub-steps are individually addressable (`--step a1`, `--step a2`). Range syntax works too (`a-b` → `a1,a2,b`). - **Migration**: on startup, legacy `step-a-*` files are renamed to `step-a1-*` so existing output dirs remain usable without re-running A1. - **Legacy rollup diagnostics**: when `AGGCHAINTYPE()` fails (pre-aggchainbase contract), `logLegacyRollupInfo` queries the rollup manager to surface `rollupID`, `rollupTypeID`, `chainID`, `forkID`, `rollupVerifierType`, and full `rollupTypeMap` info (consensusImpl, verifier, obsolete, genesis, programVKey) as diagnostic log lines. ##⚠️ Breaking Changes - 🔌 **CLI**: `--step a` still works as before (runs both sub-steps). `step-a1-addresses.json` and `step-a1-failed-traces.json` are the new canonical A1 outputs; `step-a-failed-traces.json` is no longer written directly (migrated from legacy on startup). ## ✅ Testing - 🤖 **Automatic**: `TestParseStepList` extended with `a`, `a-b`, `a2-b` cases. - 🖱️ **Manual**: run `--step a` on a chain with trace failures to verify A2 recovers addresses from receipts. ## 📝 Notes - A2 never aborts on receipt failures — it logs a warning and skips, so the pipeline always produces a result even when receipts are also unavailable. - The legacy rollup diagnostics path does not modify check failures or results; it is purely informational output to help diagnose pre-aggchainbase deployments. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…act holder decomposition (#1632) ## 🔄 Changes Summary - **Step B2**: probes each contract address collected in Step A for the ERC-20 interface (`totalSupply`/`balanceOf`). Classifies contracts as `DetectedERC20` (holds ≥1 tracked wrapped token) or `DiscardedERC20`. Outputs `step-b2-detected-erc20s.json` and `step-b2-discarded-erc20s.json`. - **Step B3**: iterates over `options.extraErc20Contracts`. Reuses B2 holder data when available; otherwise calls `balanceOf` for every EOA from Step A. Outputs `step-b3-erc20-holders.json`. - **Step C**: extended to incorporate SC-locked values from B2 detected ERC-20s. - **Step D**: generates `BridgeExit` entries for ERC-20-locked balances from B2/B3. - **config**: added `extraErc20Contracts` option; increased `defaultStepAWindowSize` from 5 000 to 150 000. - Pipeline (`run.go`) updated to execute B2 and B3 between B and C. - New types, RPC helpers, unit tests, docs and example config for the new steps. ##⚠️ Breaking Changes - 🛠️ **Config**: new optional field `options.extraErc20Contracts` (array of addresses). No breaking change — defaults to empty. ## 📋 Config Updates ```json "options": { "stepAWindowSize": 150000, "extraErc20Contracts": ["0xTokenAddress1", "0xTokenAddress2"] } ``` ## ✅ Testing - 🤖 **Automatic**: unit tests added for Step B2 (`step_b2_test.go`) and Step B3 (`step_b3_test.go`); Step C tests extended. - 🖱️ **Manual**: run full pipeline with `extraErc20Contracts` populated and verify `step-b2-detected-erc20s.json`, `step-b3-erc20-holders.json` outputs. ## 🐞 Issues - Closes agglayer/pm#341 ## 📝 Notes - Step B3 short-circuits when `extraErc20Contracts` is empty — no RPC calls made. - `defaultStepAWindowSize` raised to 150 000 to reduce RPC round-trips on chains with large block ranges. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…shadow-fork verification (#1633) ## 🔄 Changes Summary Speeds up and reworks **Step G** (NewLocalExitRoot), splitting it into **G1** and **G2** and adding an off-chain computation path. **Step G split** - **G1** (`step_g1.go`): lite-syncs the L2 bridge history from genesis up to the target block into a persistent lite DB, using the new `bridgesyncerlite` package (reads `BridgeEvent` logs in parallel and builds a bridge exit tree byte-for-byte compatible with `bridgesync`). Resolves the shadow-fork block. - **G2** (`step_g2.go`, formerly `step_g.go`): computes `NewLocalExitRoot`. - **Default** (`verifyNewLocalExitRootUsingShadowFork=true`): spins up the Anvil shadow-fork, replays every bridge exit in parallel (send/collect pipeline), reorders the certificate to the on-chain deposit order, and **verifies** the lite exit tree root against the contract's `getRoot()`. - **Off-chain** (`=false`): computes the root purely from the lite tree (G1 bridges + the certificate's exits, in order) — no Anvil. **Step I always uses the reordered certificate** - In single-step mode, Step I now **always** reads `step-g-reordered-certificate.json` (run Step G first) instead of falling back to the capped/Step-E certificates, so the final certificate always matches the computed `NewLocalExitRoot`. (`runAll` already flowed the in-memory reordered cert.) **Removed** - `options.depositOrderSource` (the `events`/`bridgesync` modes) and the production-bridgesync recovery (`step_g_bridgesync.go`). Deposit order now comes from the replay's `BridgeEvent`s (shadow-fork) or the certificate order (off-chain). `StepGResult.ShadowForkFirstBlock` dropped. **New `bridgesyncerlite` package** - Minimal bridge syncer: parallel `eth_getLogs`, persists `BridgeEvent` leaves and builds the exit tree. Supports a **DB-only** mode (no RPC) so G2 can insert pre-collected leaves and build the tree without touching Anvil. Aborts on events that invalidate a `BridgeEvent`-only reconstruction (`SetSovereignTokenAddress`, `MigrateLegacyToken`, `RemoveLegacySovereignTokenAddress`, `BackwardLET`, `ForwardLET`) unless `ignoreUnsupportedL2Events` is set. >⚠️ On mainnet Step G replays ~915k bridge exits; the previous serial execution took ~4 days (~2.8 bridges/s). The parallel replay + off-chain option address this. ##⚠️ Breaking Changes - 🛠️ **Config**: `exitAddress` is now **mandatory** — `LoadConfig` errors when it is missing or set to the zero address (`0x00…00`). Configs that previously omitted it (it defaulted to the zero address) now fail. SC-locked value is bridged to this address and can only be recovered by signing from an address whose private key the operator controls. - 🛠️ **Config**: option renames (to the `ignore*` convention) — `abortOnGenesisBalance` → `ignoreGenesisBalance` *(polarity inverted: default `false` = abort)*, `continueOnTraceError` → `ignoreOnTraceError`, `continueIfBalanceMismatch` → `ignoreBalanceMismatch`. - 🗑️ **Deprecated Features**: removed `options.depositOrderSource`; removed the `config-examples/` `.json` variants (converted to `.toml`). ## 📋 Config Updates **Config accepts JSON _or_ TOML** - `LoadConfig` selects the format by file extension: `.toml` is parsed as TOML, anything else (`.json`/no extension) as JSON. TOML is normalized to JSON internally (`tomlToJSON`) so both formats share one parsing/validation path, including `signerConfig` (`json.RawMessage`) and `agglayerClient`. Field names are identical in both formats. - Added `parameters.toml.example` (each field commented with its description + default) and converted the `config-examples/` to TOML (`zkevm-cardona.toml`, `zkevm-mainnet.toml`); removed the `.json` variants. `.gitignore` now also ignores `parameters.toml`. **`exitAddress` validation** - `LoadConfig` now rejects a missing or zero-address `exitAddress`. Docs/examples updated (the field was previously documented as optional, defaulting to the zero address) and `exitAddress` ships commented-out in the example configs so the operator must set their own. **New options** - `options.verifyNewLocalExitRootUsingShadowFork` — `true` (default). `true` verifies the LER on the Anvil shadow-fork (requires Anvil); `false` computes it off-chain from the lite tree (no Anvil, trusts off-chain leaf encoding/metadata). - `options.ignoreUnsupportedL2Events` — `false` (default). Downgrades the lite syncer's abort on unsupported events to a warning. **Renamed options** (to the `ignore*` convention) - `abortOnGenesisBalance` → `ignoreGenesisBalance` *(polarity inverted: default `false` = abort)* - `continueOnTraceError` → `ignoreOnTraceError` - `continueIfBalanceMismatch` → `ignoreBalanceMismatch` **Removed** - `options.depositOrderSource`. ## ✅ Testing - 🤖 **Automatic**: `go test ./tools/exit_certificate/...` passes (incl. `bridgesyncerlite`, `step_g_order_test.go`, and `config_test.go` with the new `TestLoadConfig_MissingExitAddress` / `TestLoadConfig_ZeroExitAddress`). `go build`, `go vet`, `gofmt`, and `golangci-lint` clean. - 🖱️ **Manual**: run `--step g` (G1+G2) and Step I; confirm `step-g-new-local-exit-root.json` + `step-g-reordered-certificate.json` are produced and the lite tree root matches the contract `getRoot()` in verify mode. - 🌐 **Mainnet**: the off-chain computation was tested on mainnet against the shadow-fork and both produced the same `LocalExitRoot` (shadow-fork verification took 13.5h, with a total of 975,646 bridges generated). ## 🐞 Issues - Closes agglayer/pm#352 (agglayer/pm#352) - Closes agglayer/pm#348 (agglayer/pm#348) ## 📝 Notes - `--step g` runs G1+G2; `g1`/`g2` run individually; `g` expands to `g1,g2` in ranges. - Anvil (Foundry) is required only in the default shadow-fork verification mode. - Targets `feature/exit-certificate-tool` (the exit-certificate integration branch), not `develop`. - **Step G1 ETA refinement**: the fetch-progress ETA now measures throughput over a trailing time window instead of the lifetime average, so it is not skewed by the fast empty low-block windows at the start. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… data (#1650) ## 🔄 Changes Summary - **New `tools/exit_certificate_claimer` service**: a read-only HTTP service that, given a destination address, lists the bridge exits available for that address and assembles the full set of parameters needed to call `AgglayerBridge.claimAsset` on L1. It builds the `claimAsset` arguments from three sources: the signed certificate (`exit-certificate-signed.json`), the L2 local exit tree (`step-g-l2bridgesyncerlite.sqlite`) and the L1 Info Tree DB. API base path `/claimer/v1`, endpoints: `GET /health`, `GET /bridges`, `GET /claim-params`. See `SPEC.md` / `README.md`. - Proofs are anchored to the L1 settlement leaf; the certificate's `new_local_exit_root` must already be settled on L1 (`/claim-params` returns `409` if not). - Config can be provided directly (JSON/TOML, see `service/config.toml.example`) or **derived from an `exit_certificate` config** via `--exit-certificate-config`. - On startup the claimer derives the settlement GER from the WAIT step result and either serves from the already-synced L1 Info Tree DB or syncs L1 only until that GER is indexed. - **Helper scripts** (`tools/exit_certificate_claimer/scripts/`): `list-bridges.sh`, `claim-asset.sh`, and `claim-all.sh` (claims every pending deposit for all addresses of an exit run). - **`exit_certificate` tool changes** required for the claimer flow: - **WAIT step**: confirm L1 settlement and persist `step-wait-result.json`; handle `verifyBatches` events. - **Step F**: admin toggle. - **Step G2**: generate metadata when the shadow fork is off; use raw metadata. - **Step H**: refuse to proceed when the agglayer still has a non-settled (open) certificate for the network; clearer LocalExitRoot mismatch error. - **Build**: `make build-tools` now also builds `exit_certificate_claimer`, and individual `build-<tool>` targets were added (`build-exit_certificate`, `build-exit_certificate_claimer`, etc.). ##⚠️ Breaking Changes - 🛠️ **Config**: None to existing components — the new config is scoped to the new tool. - 🔌 **API/CLI**: Adds a new standalone `exit_certificate_claimer` binary; no changes to existing interfaces. The claimer's default HTTP port is `7080`. - 🗑️ **Deprecated Features**: None. ## 📋 Config Updates - 🧾 New tool config only: `tools/exit_certificate_claimer/service/config.toml.example` (or derive it from an existing `exit_certificate` config via `--exit-certificate-config`). ## ✅ Testing - 🤖 **Automatic**: `service/certificate_test.go`, `service/claimer_test.go`, and `exit_certificate/step_wait_verifybatches_test.go` (plus updated step_f / step_g2 / config tests). Verified locally: `make build`, `go test ./...` (pass), and `golangci-lint run` (0 issues). - 🖱️ **Manual**: Build with `make build-exit_certificate_claimer`, run the service against an exit-certificate output dir (`--exit-certificate-config`), then exercise the scripts (`list-bridges.sh`, `claim-asset.sh`, `claim-all.sh`). ## 🐞 Issues - Related: agglayer/pm#364 ## 🔗 Related PRs - Builds on #1633 (Step G1/G2 split + `bridgesyncerlite`), already merged into `feature/exit-certificate-tool`. ## 📝 Notes - Backend design and API documented in `tools/exit_certificate_claimer/{SPEC,README}.md`; scripts documented in `tools/exit_certificate_claimer/scripts/README.md`. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
7eb9e06 to
427ae79
Compare
…aim flow - Build via `make build-exit_certificate`; run with `./target/exit_certificate` - Add Requirements section (PP, threshold=1, prior settled certificate, stopped sequencer) - Add "no unclaimed L1→L2 bridges" limitation - Document zkEVM config-examples and the missing options (ignoreUnsupportedL2Events, verifyNewLocalExitRootUsingShadowFork) - Fix step descriptions to match code: Step A (A1/A2), SUBMIT (pending-cert rejection + L1 block capture), WAIT (L1 settlement confirmation) - Document `--step` ranges (a-c, g-, 0-wait) - Note SUBMIT/WAIT must be run explicitly after the pipeline - Replace Output section with a Result section covering the claim files consumed by exit_certificate_claimer - Drop the obsolete external getLBT reference Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|



🔄 Changes Summary
New
exit-certificateCLI tool undertools/exit_certificate/that generates exit certificates for aggchain migration. It scans L2 state from genesis to a target block, discovers all addresses with value (ETH + wrapped tokens), computes smart-contract-locked balances, detects unclaimed L1→L2 bridge deposits, verifies balances against the agglayer, computes the new LocalExitRoot via a shadow-fork, and produces a fully signed agglayerCertificateready for submission.Full pipeline: CHECK → 0 → A → B → C → D → E → F → G → H → I → SIGN → SUBMIT → WAIT
NewWrappedToken/SetSovereignTokenAddressevents, fetchestotalSupplyper tokendebug_traceTransactionwithprestateTracer+diffModeCertificatewithBridgeExitentriesadmin_getTokenBalance; proportional capping on mismatchNewLocalExitRootcomputation via Anvil shadow-fork of the L2 chainPreviousLocalExitRootfrom agglayer via gRPCL1InfoTreeLeafCountfrom L1)go_signerSettledorInErrorSteps can be run individually or as a full pipeline via
--stepflag. Intermediate results are persisted to an output directory, enabling step-by-step execution and resumability.Additional changes in this PR:
options.agglayerAdminToken)hex,rpc,config,step_f, andstep_gutilities📋 Config Updates
tools/exit_certificate/parameters.json(standalone JSON). Key fields:{ "l2RpcUrl": "https://your-l2-rpc.example.com", "l1RpcUrl": "https://your-l1-rpc.example.com", "l2BridgeAddress": "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe", "l2NetworkId": 1, "targetBlock": "latest", "exitAddress": "0x0000000000000000000000000000000000000001", "destinationNetwork": 0, "sovereignRollupAddr": "0x...", "l1GlobalExitRootAddress": "0x...", "signerConfig": { "Method": "local", "Path": "keystore.json", "Password": "..." }, "options": { "blockRange": 5000, "concurrencyLimit": 20, "rpcBatchSize": 200, "outputDir": "./output", "agglayerAdminURL": "https://admin-agglayer.example.com", "agglayerAdminToken": "<IAP bearer token>", "agglayerClient": { "GRPC": { "URL": "agglayer.example.com:50051", "UseTLS": true } } } }✅ Testing
tools/exit_certificate/(hex, rpc, config, step_c, step_f, step_g)🐞 Issues
📝 Notes
anvil(Foundry) in$PATHfor Step Gdebug_traceTransaction)