Skip to content

feat(exit-certificate-claimer): add backend service for serving claim data#1650

Merged
joanestebanr merged 18 commits into
feature/exit-certificate-toolfrom
feat/exit_certificate_claimer-pm364
Jun 12, 2026
Merged

feat(exit-certificate-claimer): add backend service for serving claim data#1650
joanestebanr merged 18 commits into
feature/exit-certificate-toolfrom
feat/exit_certificate_claimer-pm364

Conversation

@joanestebanr

@joanestebanr joanestebanr commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

🔄 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

📝 Notes

  • Backend design and API documented in tools/exit_certificate_claimer/{SPEC,README}.md; scripts documented in tools/exit_certificate_claimer/scripts/README.md.

@joanestebanr joanestebanr self-assigned this Jun 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread tools/exit_certificate_claimer/service/certificate.go Outdated
Comment thread tools/exit_certificate_claimer/service/claimer.go Outdated
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>
@joanestebanr joanestebanr force-pushed the feat/exit_certificate_claimer-pm364 branch from db3f45e to 5209333 Compare June 9, 2026 15:43
@joanestebanr joanestebanr changed the title feat(exit-certificate): add exit_certificate_claimer backend, bridgesyncerlite, and Step G1/G2 split feat(exit-certificate-claimer): add backend service for serving claim data Jun 9, 2026
@joanestebanr joanestebanr added the exit_certificate_tool Tool to create a final exit certificate label Jun 9, 2026
joanestebanr and others added 17 commits June 10, 2026 10:53
…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>
@sonarqubecloud

Copy link
Copy Markdown

@joanestebanr joanestebanr merged commit 7eb9e06 into feature/exit-certificate-tool Jun 12, 2026
20 of 21 checks passed
@joanestebanr joanestebanr deleted the feat/exit_certificate_claimer-pm364 branch June 12, 2026 13:12
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

exit_certificate_tool Tool to create a final exit certificate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant