Skip to content

feat: autoclaim L1 -> L2#1644

Closed
arnaubennassar wants to merge 24 commits into
developfrom
feat/autoclaim-plan
Closed

feat: autoclaim L1 -> L2#1644
arnaubennassar wants to merge 24 commits into
developfrom
feat/autoclaim-plan

Conversation

@arnaubennassar

@arnaubennassar arnaubennassar commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

🔄 Changes Summary

  • Adds an opt-in autoclaim Aggkit component for L1 -> L2 bridge claims. It discovers eligible exits from l1bridgesync, persists request/cursor/attempt state, prepares claim proofs with l1infotreesync plus a per-claimer l2gersync, submits claimAsset / claimMessage transactions through EthTxManager, and tracks requests through confirmation or failure.
  • Adds per-destination claimers with named policies: allow-all, api-approve, no-message, and basic-filter.
  • Adds optional Auto Claim REST endpoints under /autoclaim/v1 for request listing, inspection, manual approval, and manual rejection, with generated Swagger docs.
  • Wires Auto Claim into startup through the new autoclaim component, config loading/validation/defaults, docs, mocks, and the OP e2e environment.
  • Hardens the bridge-service L2 deposit L1 info tree lookup used by /l1-info-tree-index: if a verified batch LER is missing from the local L2 exit tree, the lookup now avoids mixing L2 bridge-sync block numbers with L1 verified-batch block numbers and falls back to a bounded linear scan.

⚠️ Breaking Changes

  • 🛠️ Config: No breaking migration. Auto Claim adds a new optional [AutoClaim] config section and remains disabled by default.
  • 🔌 API/CLI: Adds a new optional autoclaim component selector and optional /autoclaim/v1 API. Existing bridge service API paths are unchanged.
  • 🗑️ Deprecated Features: None.

📋 Config Updates

  • 🧾 Diff/Config snippet: Adds defaults for the new disabled-by-default Auto Claim section:
[AutoClaim]
Enabled = false
StoragePath = "{{PathRWData}}/autoclaim.sqlite"
Claimers = []

[AutoClaim.API]
Enabled = false
Host = "0.0.0.0"
Port = 5579

[AutoClaim.L1ToL2Watchdog]
Enabled = true
StartBlock = 0
PollInterval = "3s"
RetryAfterErrorPeriod = "1s"
MaxRetryAttemptsAfterError = -1
EtrogL1UpgradeBlock = 0

[AutoClaim.L2ToLxWatchdog]
Enabled = false
  • Enabling Auto Claim requires both selecting the autoclaim component and setting [AutoClaim].Enabled = true.
  • Each enabled [[AutoClaim.Claimers]] entry configures one EVM destination network with its own RPC URL, bridge address, policy, gas settings, retry settings, and EthTxManager storage/signing config.
  • Auto Claim owns AutoClaim.StoragePath; each claimer also has an independent EthTxManager.StoragePath and isolated per-claimer l2gersync / L2 reorg detector databases under the Auto Claim storage directory.
  • L2 -> Lx Auto Claim remains unsupported and must stay disabled.

✅ Testing

  • 🤖 Automatic: Added unit coverage across autoclaim/api, claimer, claimtx, config, policy, proof, runtime, sender, simulator, storage, types, and watchdog.
  • 🤖 Automatic: Added Go e2e coverage for TestAutoClaimL1ToL2AllowAll, TestAutoClaimL1ToL2APIApprove, and TestAutoClaimL1ToL2BasicFilter.
  • 🤖 Automatic: Current PR checks show lint, test-unit, Go e2e, Docker image build, CodeQL, SonarCloud, and the configured multi/single-chain e2e workflows passing. Run govulncheck is currently failing.
  • 🖱️ Manual: No separate manual testing was run while refreshing this PR description.

🐞 Issues

🔗 Related PRs

  • None listed.

📝 Notes

  • Auto Claim currently supports L1 -> L2 only. L2 -> Lx discovery is reserved for future work.
  • Request IDs are derived from origin_network:destination_network:deposit_count; origin_network is the bridged token's origin network, not necessarily the chain where the bridge was initiated.
  • GER readiness is checked during proof preparation by each claimer's own l2gersync, which supports both legacy and sovereign L2 GER manager behavior.
  • The optional api-approve policy requires the Auto Claim API so operators can approve or reject requests in manual-approval-required status.
  • This branch currently skips five known flaky remove-GER e2e entrypoint tests: TestRemoveGER_NoProblematicClaims, TestRemoveGER_CategoryA, TestRemoveGER_CategoryB1, TestRemoveGER_CategoryB2, and TestGenerateInvalidGER. This is being worked on feat: migrate e2e tests from bats to go #1643
  • Bridge-service changes are a scoped regression fix for L2-origin bridge proof lookup. The previous fallback could call GetFirstVerifiedBatchesAfterBlock with GetLastRoot().BlockNum, which is an L2 bridge-sync block number even though the L1 info tree query expects an L1 block number. The new path only falls back on db.ErrNotFound, keeps the original L1 verified-batch bounds, and skips missing LERs with a linear scan when binary search cannot safely continue.

@arnaubennassar arnaubennassar marked this pull request as draft June 8, 2026 09:06
Comment thread autoclaim/storage/storage.go Fixed
@arnaubennassar arnaubennassar changed the title Feat/autoclaim plan feat: autoclaim plan Jun 9, 2026
@arnaubennassar arnaubennassar self-assigned this Jun 9, 2026
# Conflicts:
#	multidownloader/evm_multidownloader_reorg.go
#	multidownloader/evm_multidownloader_reorg_test.go
Comment thread autoclaim/storage/storage.go Fixed
@arnaubennassar arnaubennassar changed the title feat: autoclaim plan feat: autoclaim L1 -> L2 Jun 11, 2026
arnaubennassar and others added 12 commits June 11, 2026 10:57
…tic logs

- config/config_test.go: move testify/urfave imports to end of non-stdlib
  group to satisfy gci two-group ordering (fixes CI lint failure)
- config/default.go: set AutoClaim.L1ToL2Watchdog Enabled = true to match
  the assertion in TestLoadDefaultConfig (fixes unit test failure)
- autoclaim/proof/preparer.go: add debug logs in selectL1InfoTreeIndex to
  surface whether the proof is blocked by l1InfoTree lag or gerSyncer lag
- autoclaim/claimer/claimer.go: log info when proof not ready so E2E logs
  clearly show the pending state on each poll cycle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rackedBlocks wipe

reorgDetector.Start calls loadTrackedHeaders which replaces the in-memory
trackedBlocks map entirely from DB. If l2gersync.New (which calls Subscribe
internally) ran first, the subscription entry it added would be wiped by the
subsequent loadTrackedHeaders call, causing every AddBlockToTrack to return
"subscriber not subscribed" forever.

The per-claimer l2gersync was stuck in a retry loop at the first GER injection
block (block 24), never processing the GER that covered the bridge deposit, so
GetFirstGERAfterL1InfoTreeIndex always returned ErrNotFound and the claim proof
was never prepared.

Fix: call reorgDetector.Start synchronously before l2gersync.New so that
loadTrackedHeaders completes before Subscribe adds the entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 3 SQL injection hotspots in autoclaim/storage/storage.go were marked
SAFE on SonarCloud (all use parameterized ? placeholders, not user input).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add tests across 11 files covering uncovered nil-arg error paths, option
closures, getter/accessor methods, disabled-state branches, and constructor
validation to push SonarCloud new_coverage above the 80% quality gate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…overage

Adds 7 targeted tests across sender and api packages to cover
previously unreachable lines: default time.Now closure bodies,
IsClaimed error propagation, pollResult Result() failure path,
zero WaitPeriod fallback to s.pollPeriod, zero-MaxRetries target
fallback to request.MaxRetries, and claimTxHash with nil Tx entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@agglayer agglayer deleted a comment from claude Bot Jun 12, 2026
@arnaubennassar arnaubennassar marked this pull request as ready for review June 12, 2026 07:52
@joanestebanr

Copy link
Copy Markdown
Collaborator

@claude

Comment thread docs/autoclaim.md
Comment thread autoclaim/types/types.go Outdated
Comment thread docs/autoclaim.md Outdated
Comment thread autoclaim/runtime/runtime.go

@joanestebanr joanestebanr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Another point is that the API for bridgeservice and for autoclaim is very similar. But there are a different that can be a bit misleading: the first page_number: bridgeservice use 1 and autoclaim use 0

…overrides, dedup)

Address the unresolved review threads on PR #1644:

- API design: move the public read endpoints (will/will-not-claim) into the
  bridge service under /bridge/v1/autoclaim/bridges, served only when the
  autoclaim component runs; keep admin approve/reject on the standalone
  [AutoClaim.API]. Shared DTOs/parsing extracted to autoclaim/apitypes.
- Replace the redundant [AutoClaim] Enabled flag with DryRun (component list
  already gates running); DryRun prepares claims but skips submission, ending
  requests in a new terminal "dry-run" status.
- Allow per-claimer BlockFinality / InitialBlockNum overrides for the L2 GER
  syncer instead of always inheriting the shared [L2GERSync] values.
- Deduplicate the pre-Etrog global-index computation into
  bridgesync.GlobalIndexForBridge, used by both bridgeservice and autoclaim.
- Harden the admin API: max-length validation on decider/decider_id/reason and
  an io.LimitReader body cap.

Updated docs, swagger, and tests accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@arnaubennassar

Copy link
Copy Markdown
Collaborator Author

Review comments addressed

Pushed 2883c4f addressing the open threads.

API redesign (the main one). Split endpoints by audience so operators can expose request status without exposing admin controls:

  • Public, read-only inspection moved into the bridge service: GET /bridge/v1/autoclaim/bridges[/{id}] on the shared [REST] host/port, registered only when the autoclaim component runs (the bridge service gets an optional read-only AutoClaimQuerier, wired in run.go by opening the autoclaim storage once and sharing the handle).
  • Admin approve/reject stay on the standalone [AutoClaim.API] (own host/port, can be firewalled). Shared DTOs + query parsing extracted to autoclaim/apitypes. This also answers the "share host/port with Bridge" comment.

EnabledDryRun. Removed the redundant top-level [AutoClaim] Enabled (running is gated by the --components list); added DryRun, which runs the full pipeline but skips submitting the claim tx, ending requests in a new terminal dry-run status.

Per-claimer GER config. [[AutoClaim.Claimers]] now accepts optional BlockFinality / InitialBlockNum overrides, applied over the shared [L2GERSync] defaults in newGERSyncer.

Dedup. The pre-Etrog/global-index computation is now a single helper bridgesync.GlobalIndexForBridge, used by both bridgeservice and autoclaim.

Admin API hardening (flagged by the bot): max-length validation on decider/decider_id/reason and an io.LimitReader body cap.

Docs, swagger, and tests updated; the resolved CodeQL/bot threads were already outdated. Bot "suggested/optional" items (errgroup startup, metrics, etc.) left for follow-ups.

@sonarqubecloud

Copy link
Copy Markdown

…-test health check

The default op-pp config runs with --components=...,autoclaim. Removing the
top-level [AutoClaim] Enabled = false left the L1ToL2Watchdog and allow-all
claimer active by default, causing autoclaim to race with the manual claim in
the post-test bridge health check and revert it.

Disable the claimer (Enabled = false) in the default config so autoclaim starts
but processes no bridges outside explicit test setups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@agglayer agglayer deleted a comment from claude Bot Jun 18, 2026
arnaubennassar and others added 2 commits June 18, 2026 16:25
SonarCloud's scanner auto-configures for PRs by verifying the PR via GitHub
API using GITHUB_TOKEN. Without an explicit pull-requests:read permission the
token falls back to org-default read-only (contents only), causing the scanner
to fail with "Something went wrong while trying to get the pullrequest".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

L1 -> L2 Autoclaim

3 participants