feat(exit-certificate-claimer): add backend service for serving claim data#1650
Merged
joanestebanr merged 18 commits intoJun 12, 2026
Merged
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db3f45e046
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Add the exit_certificate_claimer backend: an HTTP service that derives and serves claim data (certificate, claimer, L1 info tree, local exit tree) on top of the exit_certificate tool and bridgesyncerlite. Wire its build target into `make build-tools`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
db3f45e to
5209333
Compare
…toggle Step SUBMIT now captures the latest L1 block right before sending the certificate (l1LatestBlockBeforeSubmittingCertificate) and requires l1RpcUrl. Step WAIT: - Refactor: poll GetCertificateHeader by hash until final (drop the GetLatestPendingCertificateHeader phase); receives the full StepSubmitResult. - After settlement, scan the RollupManager on L1 from the captured block to finalized for the VerifyBatchesTrustedAggregator event matching rollupID and the certificate's NewLocalExitRoot, recording the L1 block and tx hash. The RollupManager address is rollupManagerAddress (new optional config), else resolved on-chain from sovereignRollupAddr.rollupManager(). - In that same L1 block, record the last UpdateL1InfoTree and UpdateL1InfoTreeV2 events from l1GlobalExitRootAddress. Step F: add useAgglayerAdminToStepFCheck toggle (default true) to skip the agglayer admin balance check when no admin endpoint is available. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds per-tool make targets (build-exit_certificate, build-exit_certificate_claimer, build-remove_ger, build-aggsender_find_imported_bridge) so each tool can be built on its own. The ## descriptions now live on these aliases so they show in `make help`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement Claimer.Check to confirm the certificate's NewLocalExitRoot is the local exit root settled for this network in the rollup exit tree captured by the WAIT step (waitResult.UpdateL1InfoTree.RollupExitRoot), via l1.GetLocalExitRoot. Returns ErrLocalExitRootNotSettled on mismatch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s, and build targets Wire the WAIT step result into the claimer (waitresult loader, derive/config, run, l1infotree) and add helper scripts (list-bridges, claim-asset). Includes accompanying exit_certificate updates (Makefile build targets, step_e, run, docs/config examples). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When Step G2 ran without the shadow fork (options.verifyNewLocalExitRootUsingShadowFork=false), it trusted each BridgeExit's own Metadata field to encode the lite-tree leaf. Step D builds the exits with empty metadata, so any exit whose on-chain leaf carries non-empty metadata -- e.g. an L2-native token, where the bridge contract encodes (name, symbol, decimals) -- got a wrong leaf encoding and therefore an incorrect NewLocalExitRoot, with nothing to verify it against in off-chain mode. generateMetadata now reconstructs each exit's metadata exactly as AgglayerBridge.bridgeAsset does (gasTokenMetadata for native exits, getTokenMetadata(token) for ERC-20s), querying the bridge contract through the fork backend -- the Anvil shadow fork when it runs, otherwise a backend over the real L2 -- so both modes produce the same leaves the contract does. The lite tree is built from that raw metadata (the syncer keccak256-hashes it, mirroring the contract), and each exit's Metadata field is set to keccak256(raw): agglayer's BridgeExit.Hash() plugs that field straight into the leaf hash, so the certificate must carry the hash for its recomputed LER to match NewLocalExitRoot. When the shadow fork runs, compareMetadata cross-checks the generated metadata against the on-chain metadata recovered from the replay. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…use raw metadata - Anchor claim proofs to the L1 info tree leaf the certificate settled at (via GetInfoByGlobalExitRoot on the WAIT-step GER) instead of the latest leaf, whose newer rollup exit root no longer contains the certificate's NewLocalExitRoot. - Compute the exit-tree leaf hash directly (replicating agglayer BridgeExit hashing) since the certificate stores metadata already hashed; re-hashing via bridgesyncerlite would double-hash and never match the local tree. - Expose raw bridge metadata from the local exit tree and pass it to claimAsset (the bridge contract hashes it itself). - claim-asset.sh: wait for the tx receipt and verify execution status, failing on revert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…80, guard against pending certificates - Add claim-all.sh to claim every pending deposit for all addresses of an exit run - Change claimer default port from 8080 to 7080 - Refuse Step H when the agglayer has a non-settled (open) certificate for the network - Clarify the LocalExitRoot mismatch error to point at sequencer state changes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…itresult, types and claimer Cover the pure-logic surface of the claimer service: config loading (JSON/TOML, defaults, validation, path resolution), HTTP handlers via httptest (health/bridges/claim-params, 400/409 paths), wait-result parsing and settlement GER derivation, serialization helpers, and additional Claimer cases (Check, deposit-count filter, error branches). Also cover the no-RPC validation branches of resolveRollupManager. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onfig validation error Add unit tests for LocalExitTree.DepositCount/Metadata/Proof/Close (constructing the struct directly, with a stub ReadTreer for Proof) and the LoadConfig branch where a well-formed config fails validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The HTTP server only starts after OpenL1InfoTree has synced the L1 Info Tree up to the certificate's settlement GER, so /health always returns "ok" — there is no syncing state. Correct the SPEC accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… L1 sync Make explicit that the HTTP server does not bind until OpenL1InfoTree has caught the L1 Info Tree up to the settlement GER, so a reachable endpoint is always ready (matching /health always returning "ok"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- check type assertions in WAIT verifybatches test helpers (forcetypeassert) - extract eth_getLogs/eth_getBlockByNumber RPC method constants and reuse the existing eth_call constant (goconst) - name the UpdateL1InfoTree minimum-topics magic number (mnd) - drop a stray leading newline in Step G2 metadata comparison (whitespace) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract resolveBlockFinality and wire gerIndexed/waitForGER to the gerProber interface so they can be exercised without a real L1 syncer, and add unit tests covering finality parsing, GER indexing, wait-for-GER cancellation, and the sync-disabled OpenL1InfoTree guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…laimer Add unit tests driving the pipeline steps and claimer service through JSON-RPC stubs, SQLite fixtures, a real keystore and an agglayer client mock, covering the run.go orchestration wrappers, RunStepE/A/A2/Sign, step_wait L1 confirmation helpers, step_g2 metadata/RPC helpers, the SetSovereignTokenAddress overrides, and the claimer derive/server/ localexittree/BuildClaimParams paths. Raises combined statement coverage from ~66% to ~81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract a client-injectable runStepH from RunStepH (mirroring runStepG2's launcher injection) and unit-test the network-info logic with the agglayer client mock: the pending/open-certificate rejection guard, the settled-LER derivation, the Step G InitialLocalExitRoot cross-check, and the GetNetworkInfo error path. runStepH is now fully covered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…capture Extract a client-injectable runStepSubmit from RunStepSubmit (same pattern as runStepH) and unit-test it with the agglayer client mock: the non-closed pending-certificate rejection, the l1RpcUrl guard, the latest-L1-block capture (success and RPC error), the SendCertificate path and error. runStepSubmit is now fully covered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… client Extract a client-injectable runStepWait from RunStepWait (same pattern as runStepH/runStepSubmit) and unit-test it with the agglayer client mock: the settled happy path with full L1 settlement confirmation, and the InError rejection. Use the rpcMethodEthGetBlockByNumber constant in the L1 stubs to satisfy goconst. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
7eb9e06
into
feature/exit-certificate-tool
20 of 21 checks passed
joanestebanr
added a commit
that referenced
this pull request
Jun 12, 2026
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



🔄 Changes Summary
tools/exit_certificate_claimerservice: 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 callAgglayerBridge.claimAsseton L1. It builds theclaimAssetarguments 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. SeeSPEC.md/README.md.new_local_exit_rootmust already be settled on L1 (/claim-paramsreturns409if not).service/config.toml.example) or derived from anexit_certificateconfig via--exit-certificate-config.tools/exit_certificate_claimer/scripts/):list-bridges.sh,claim-asset.sh, andclaim-all.sh(claims every pending deposit for all addresses of an exit run).exit_certificatetool changes required for the claimer flow:step-wait-result.json; handleverifyBatchesevents.make build-toolsnow also buildsexit_certificate_claimer, and individualbuild-<tool>targets were added (build-exit_certificate,build-exit_certificate_claimer, etc.).exit_certificate_claimerbinary; no changes to existing interfaces. The claimer's default HTTP port is7080.📋 Config Updates
tools/exit_certificate_claimer/service/config.toml.example(or derive it from an existingexit_certificateconfig via--exit-certificate-config).✅ Testing
service/certificate_test.go,service/claimer_test.go, andexit_certificate/step_wait_verifybatches_test.go(plus updated step_f / step_g2 / config tests). Verified locally:make build,go test ./...(pass), andgolangci-lint run(0 issues).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 PRs
bridgesyncerlite), already merged intofeature/exit-certificate-tool.📝 Notes
tools/exit_certificate_claimer/{SPEC,README}.md; scripts documented intools/exit_certificate_claimer/scripts/README.md.