feat: auto claim L2->L1 and L2->L2 support#1705
Conversation
…works Add test/e2e/envs/op-pp-2chains/ (summary.json, docker-compose.yml, config/001, config/002, config/agglayer) and the EnvOpPP2Chains env constant. Generalize the loader so it can expose both L2 networks without changing the single-chain EnvOpPP behavior: - Add optional L2B *L2Config to Env (nil for single-chain envs). - Extract per-L2 wiring into loadL2Config; LoadEnv loads "001" -> L2 always and "002" -> L2B only for EnvOpPP2Chains. - L2Config now carries its own Client, BridgeService, Keys and URLs so each L2 is self-contained for multi-chain tests. - Tolerate both op-geth (op-pp) and op-reth (op-pp-2chains) execution clients via summaryL2Network.l2RPCExternal(). - waitForServices now waits on every L2 network rather than only the first. checks.go: extract L2 connectivity/contracts/bridge-service checks into reusable *For helpers, run them for L2B when present, and gate the hardcoded chain-ID assertions by env name (op-pp: 271828/2151908, op-pp-2chains: L2A 20201 / L2B 20202). loader_test.go: add pure-parse tests asserting op-pp-2chains parses into two L2 networks (20201, 20202) with distinct endpoints, op-pp still parses to one (2151908), and that op-pp-2chains/summary.json exists. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add BridgeL2ToL2 helper in bridge_utils.go that bridges an asset from L2A to L2B through the aggkit bridge service: send BridgeAsset on L2A with L2B's network id as destination, poll L2A bridge service for the deposit, wait for L1 Info Tree inclusion, then poll the DESTINATION (L2B) bridge service for GER propagation (GetInjectedL1InfoLeaf), fetch the claim proof from L2B, ClaimAsset on L2B, and assert the destination balance increased. Add TestBridgeL2ToL2 which skips when testEnv.L2B is nil (single-chain env) and fails loudly on any bridge-service or on-chain error. Wire AGGKIT_E2E_ENV in TestMain to select the env (default op-pp) so CI can run the op-pp-2chains matrix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matrixize test-go-e2e across [op-pp, op-pp-2chains] with fail-fast:false and AGGKIT_E2E_ENV selector. Pull both envs' images in pull-docker-images. Bump timeout to 60m for GER-propagation headroom. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r startup InsertBlock is now idempotent: a SQLite constraint violation (block already present) is treated as a successful no-op. This fixes the startup race where the L2ClaimSyncer auto-start goroutine and the aggsender's SetClaimSyncerNextRequiredBlock loop both bootstrap the same block (block 0) concurrently, one failing with 'UNIQUE constraint failed: block.num' and leaving the aggsender stuck forever in starting_claim_syncer_stage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in both chains
OP-reth in this test env never produces a "finalized" L2 block
(eth_getBlockByNumber("finalized") always returns 0). With the previous
FinalizedBlock setting, the EVMDownloader's safe-zone check
(requestToBlock <= lastFinalizedBlock) never passed for the L2ClaimSyncer
when scanning empty blocks, causing it to loop forever at block 0.
This kept BridgeDataQuerier.GetLastProcessedBlock (which returns min of
bridge syncer and claim syncer) at 0, so the aggsender saw "no new blocks"
indefinitely and never sent a certificate — blocking the L1 info tree update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ClaimSync EVMDownloader was passed rd.GetFinalizedBlockType() as the
finalizedBlockType parameter. On OP chains (like op-pp-2chains test env),
the reorg detector uses FinalizedBlock but OP-reth never produces a
finalized block tag — eth_getBlockByNumber("finalized") returns 0
indefinitely. This caused the EVMDownloader safe-zone check
(requestToBlock <= lastFinalizedBlock) to never pass when scanning ranges
with no events, permanently stalling the L2ClaimSyncer at block 0 and
blocking the aggsender's BridgeDataQuerier (which gates on min(bridge,claim)).
Fix: pass cfg.BlockFinality instead, so the ClaimSync downloader uses its
own configured finality (LatestBlock) independent of the reorg detector's
finality (which is correctly FinalizedBlock for reorg safety).
Also revert the temporary workaround in op-pp-2chains TOML configs that
changed [ReorgDetectorL2] FinalizedBlock from "FinalizedBlock" to "LatestBlock".
The reorg detector now correctly retains FinalizedBlock semantics; only the
ClaimSync downloader's safe-zone check uses the syncer's own finality.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nce assertion - Increase healthcheck start_period for op-reth and op-node to 300s so the docker compose dependency chain resolves before the test timeout fires on a cold start. - Add missing aggsender-validator-config.toml files, keystore files, and beacon spec/entrypoint overrides so all containers start correctly. - Fix op-reth entrypoint to write the genesis hash to the shared volume so op-node can consume it on first boot. - Fix testmain_test.go: extend env-load context to 15m and select env via AGGKIT_E2E_ENV so the matrix CI job loads the correct environment. - Fix BridgeL2ToL2: replace incorrect native-ETH balance check with an ERC20 balance assertion using ComputeTokenProxyAddress + MintableERC20 binding; update test to mint/approve the MintableERC20 token before bridging. - Test passes end-to-end from cold start in ~250s including post-test health check (L1<->L2 and L2<->L1 bridge flows). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Leftover .bak files from the snapshot generation process that were not deleted in the prior commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scope the op-pp-2chains CI job to only run TestBridgeL2ToL2 via a TEST_RUN Makefile variable, so existing single-chain tests (BackwardForwardLET, RemoveGER, etc.) don't run against the 2-chain env and fail. - Increase bridge-deposit polling in BridgeL2ToL2 from 30x2s (60s) to 60x5s (300s) to accommodate slower CI environments where aggkit indexing takes longer than on local. - Remove two unused //nolint:gosec directives in loader_test.go flagged by nolintlint linter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bridge service may report L1InfoTree leaf injection slightly before the op-reth RPC node exposes the new block via 'latest'. This caused ClaimAsset simulation (eth_call) to revert with 'execution reverted' because the GER wasn't visible yet in the on-chain GlobalExitRootMap. Add a direct on-chain poll of GlobalExitRootMap(gerHash) after fetching the claim proof in both BridgeL1ToL2 and BridgeL1ToL2WithResult, so the claim is only attempted once the L2 RPC node confirms the GER. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GetInjectedL1InfoLeaf returns the first GER injected on L2 with index >= the deposit's inclusion index, not necessarily the exact index. When a parallel L2->L1 bridge updates the L1InfoTree to a higher index first, the aggoracle may inject that higher index on L2A, skipping the lower one. Using the original (lower) index for GetClaimProof produces a GER hash that is absent from L2's GlobalExitRootMap, causing ClaimAsset to revert. Fix: capture the injected leaf's L1InfoTreeIndex and use it for GetClaimProof in both BridgeL1ToL2 and BridgeL1ToL2WithResult. The on-chain GER confirmation poll is kept as an additional safety check. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves and serves the bridge service URL for every network attached to
a rollup manager, from three sources in strict priority: config, on-chain
aggchainMetadata ("BRIDGE_SERVICE_URL"), and trustedSequencerURL + port
5577. Enumerates networks via agglayermanager and keeps the cache updated
via a lightweight FilterLogs polling loop with health-gated updates, wired
into cmd/run.go behind a config flag.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The package isn't consumed by anything yet — it will be wired up by a future component instead of being started directly from cmd/run.go, so drop the unused config plumbing and Start() call along with it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…outs The `docker compose up -d` startup chain for op-pp-2chains was exceeding the 15-minute LoadEnv timeout on slow CI runners. Root cause: `start_period: 300s` on all four op-reth/op-node containers creates a mandatory 600s+ serial wait (geth→beacon→op-reth→op-node) before `docker compose up -d` returns, leaving barely 240s of margin before the 900s context deadline. Changes: - op-reth-001/002, op-node-001/002: start_period 300s → 120s; interval 2s → 10s; retries 3 → 6. Each container now has 120s + 60s = 180s max to become healthy, while the worst-case serial chain drops from ~660s to ~420s. - LoadEnv context timeout: 15min → 20min (extra buffer for slow runners). - `make test-e2e` go test -timeout: 30m → 45m (keeps test/job ratio safe). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MjDD58nJmcTg7nqgt4nYZN
Checkpoint of the in-progress autoclaim L2->L1/L2->L2 work (plan steps S02-S17) so the S18 merge of the op-pp-2chains env branch can proceed on a clean tree. Squash/amend as appropriate before opening the PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JaD39SNqQsXv833hqWhiBE
…t/autoclaim-l2-lx
…inREST PR #1691's 2-chain aggkit and aggsender-validator configs predate develop's REST-section split; the current aggkit binary fatals on the deprecated `rest.port` field, crash-looping aggkit-001/002 and both validators. Mirror the working single-chain op-pp config: [PublicREST] (5577) + [AdminREST] (5579). With this the full 12-service op-pp-2chains env boots. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JaD39SNqQsXv833hqWhiBE
Wires autoclaim (L2ToLx detector + a network-2 claimer, static finder URLs for both source networks) onto the op-pp-2chains env, adds BridgeL2ToL2NoClaim and per-network aggkit stop/start/restart helpers in envs/loader.go (generalizing the "001"-hardcoded versions), and enables the autoclaim component for aggkit-002 in docker-compose.yml. Includes primeL2ToL2SourceSettlement, the network-1-side analogue of the L2->L1 test's L2-finality priming trick, needed because this env has no op-batcher/op-proposer on either L2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JaD39SNqQsXv833hqWhiBE
BridgeL2ToL1NoClaim's mine-timeout used an inline 30*time.Second literal, tripping the mnd linter. Reuse the existing bridgeMineWait constant already used by the sibling L1->L2/L2->L2 bridge helpers.
The Testing section only listed the three L1->L2 e2e tests, predating S15-S19's TestAutoClaimL2ToL1AllowAll and TestAutoClaimL2ToL2AllowAll. Document both, including the AGGKIT_E2E_ENV=op-pp-2chains selector needed for the L2->L2 test's two-rollup environment.
…nding golang.org/x/net and quic-go had known, fixed CVEs (GO-2026-5026, GO-2026-4918, GO-2026-5676); bump both. github.com/pion/dtls/v2 has a nonce-reuse CVE (GO-2026-4479) with no upstream fix and is only reachable from test helpers/dev tooling, so replace govulncheck-action with a scripted run that fails on anything outside an explicit ignore list.
GitHub's mermaid renderer failed to parse two diagrams: an unquoted flowchart edge label containing parentheses (GetURL(source)) is misread as node-shape syntax, and a semicolon inside a sequence diagram Note is treated as a statement terminator, truncating it mid-sentence.
…clusions gosec/lll/gocritic are already excluded project-wide for test files and specific rules (G115, ifElseChain) in .golangci.yml, so the explicit per-line/per-file //nolint comments added defensively are dead and trip nolintlint's unused-directive check.
… fetcher - Pin `go install golang.org/x/vuln/cmd/govulncheck` to v1.5.0 instead of @latest: SonarCloud's S8545 rule flags unpredictable dependency versions, which alone dropped the new-code security rating below the quality gate. - Add unit tests for l2_to_lx_fetcher.go (ServiceFetcher, toClaimCandidate, toTreeProof, parseAmount, parseHexBytes), which shipped with 0% coverage and was the largest contributor to the new-code coverage gate failure (new_coverage 74.2% vs required 80%).
…g env TestAutoClaimL2ToL2AllowAll called envs.LoadEnv(EnvOpPP2Chains) directly, bringing up a second docker-compose stack alongside the singleton env TestMain already started for the "op-pp" CI matrix leg. Both envs bind the same host ports (8545, 8546, 8551, ...), so under the op-pp leg (which runs the whole package with no -run filter) this reliably failed with "port is already allocated". Follow TestBridgeL2ToL2's existing pattern instead: use the shared testEnv TestMain already loaded (selected via AGGKIT_E2E_ENV) and skip when testEnv.L2B is nil. Also add the test to the op-pp-2chains matrix leg's -run filter so it actually executes against the 2-chain env.
…sed as a shell pipe Makefile passed $(TEST_RUN) unquoted to -run, so TEST_RUN="A|B" made the shell treat "|" as a pipe: `go test ... -run A | B`, running B as a command (which doesn't exist) instead of passing the whole alternation to -run.
SonarCloud's S8545 rule still flagged @v1.5.0 as an unpredictable dependency version (tags aren't treated as immutable). Pin to the exact commit the v1.5.0 tag points to instead.
|
Claude finished @arnaubennassar's task in 2m 13s —— View job Review Complete ✅I've completed a thorough review of PR #1705. Here's my assessment: 🚀 What's NewThis PR extends Auto Claim to support L2→L1 and L2→L2 bridge claiming in addition to the existing L1→L2 direction. Key additions include:
🐛 Bug FixesNone - this is a pure feature addition. 📋 Config Updates[AutoClaim.L2ToLxBridgeDetector]
Enabled = true
StartL1Block = 0 # 0 = full history
PollInterval = "3s"
RetryAfterErrorPeriod = "1s"
MaxRetryAttemptsAfterError = -1
[AutoClaim.BridgeServiceFinder]
RollupManagerAddr = "0x..." # Required when L2ToLxBridgeDetector enabled
PollInterval = "30s" # Default value
# L1-destination claimer (new capability):
[[AutoClaim.Claimers]]
Enabled = true
NetworkID = 0 # 0 now valid for L1 destination
BridgeAddr = "0x..." # L1 bridge contract
URLRPC = "http://l1-rpc:8545"
# No BlockFinality/InitialBlockNum (L1 has no l2gersync)
|
Two architectural improvements to Auto Claim: 1. Replace the per-claimer l2gersync instance with a call to the destination network's own aggkit bridge service for the GER-injection gate, discovered via bridgeservicefinder.Finder. The proof.L2GERSyncer seam now returns the covering injected leaf index directly (+ an ErrGERNotInjected sentinel), the new BridgeServiceGERGate calls the existing GET /bridge/v1/injected-l1-info-leaf endpoint, and the runtime no longer builds per-claimer SQLite DBs, L2 reorg detectors, or L2 clients. Applies to all directions incl. L1->L2. The injected-l1-info-leaf endpoint now returns 404 (not 500) when the GER is not injected yet, which the gate treats as "not ready" without burning the claim retry budget. 2. Fetch the leaf->LER Merkle proof only at claim time. /bridge/v1/claim-candidates no longer computes/returns per-bridge proofs, the L2ToLx detector no longer stores a leaf proof, and RollupPreparer always refreshes the proof from the source bridge service at claim time. Drops the leaf_proof_json column (kept ler/verify_block_num). BREAKING: L1->L2 auto claiming now requires a configured [AutoClaim.BridgeServiceFinder] and reachable destination bridge services; [[AutoClaim.Claimers]] no longer accepts BlockFinality/InitialBlockNum. Regenerated swagger + mocks; updated docs and the PR description. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ment Addresses the only actionable nit from the automated review on PR #1705 (the remaining findings are false positives, by-design, or convention-consistent — documented in the PR reply). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review. I went through each finding against the actual code. Disposition below; commit 421ba23 addresses the one actionable item. 🔴 #1 Migration safety — not applied (false positive, suggested fix is incorrect)The re-key The suggested guard 🟡 #2 COUNT(*) per page — by design
🟡 #3
|
|
4e9a694 to
d94e5f5
Compare



🚧 DRAFT — interim base branch, do not merge yet 🚧
This PR is a draft. It targets
feat/bridge-service-finder(the head branch of #1692,feat: add bridgeservicefinder package) as an interim base, notdevelop, because thisbranch was itself created on top of
feat/bridge-service-finderand also depends on #1691(
feat: add op-pp-2chains env and L2→L2 bridge test) — both still open.Merge order / what must happen before this is ready for real review:
develop.develop.feat/bridge-service-finder→develop,the draft flag is removed, and it goes up for review for real.
This branch already contains #1691's code via a direct branch merge (commit
c3e4e0f2, mergingorigin/feat/e2e-op-pp-2chains— #1691's head — directly into this feature branch, per an explicitdecision to unblock this plan's own L2→L2 e2e work without waiting for #1691 to land first). That
merge brought #1691's diff onto this branch as-is; it was not re-authored or re-reviewed here.
Reviewers: please do not review or merge this PR until #1691 and #1692 are both confirmed merged
to
developand it has been retargeted there — merging it as-is (againstfeat/bridge-service-finder, before either lands) would land unreviewed code from both of thosePRs on
developattributed to this one, and cause conflicts/duplication when they merge on theirown.
🔄 Changes Summary
bridge directions:
0).goes through the destination bridge service instead of a local GER syncer (see below); regression-tested end
to end (see Testing).
bridgedetector.L2ToLxdetectorwatches
l1infotreesyncfor each source rollup's local exit root (LER) advancing (from bothVerifyBatchesTrustedAggregator, zkEVM/state-transition rollups, and the newly-indexedVerifyPessimisticStateTransition, pessimistic/aggchain rollups — the latter previously wasn't indexed byl1infotreesyncat all), then fetches the new bridges from that source rollup's own bridge service via a newbridgeservicefinder-resolved REST call.GET /bridge/v1/claim-candidates, serves exactly that: bridges in a deposit-countrange, filtered by destination network. Backed by a new deposit-range+destination query in
bridgesync(
GetBridgesInDepositRange). It does not return a per-bridge Merkle proof — see "GER gate + claim-time proof"below.
l2gersync. Auto Claim nolonger runs a per-claimer
l2gersyncinstance (no isolated per-claimer SQLite database, no per-claimer L2 reorgdetector, no per-claimer L2 RPC client dedicated to GER tracking). Instead, during proof preparation, readiness
for an L2-destination claimer is gated by calling that destination network's own aggkit bridge service
GET /bridge/v1/injected-l1-info-leaf(resolved throughbridgeservicefinder.Finder). This applies uniformly toboth directions, including L1→L2. A claimer whose destination is L1 (
NetworkID = 0) has no such gate — itis ready as soon as
l1infotreesynchas the relevant leaf, since the GER already exists in the L1 GER manager byconstruction.
L2ToLxdetector no longer fetches or stores the leaf→LERMerkle proof at discovery time —
claim-candidatesnever returned it in this version (see above). The newrollup-origin proof preparer (
autoclaim/proof.RollupPreparer) builds the LER→rollup-exit-root proof locallyfrom
l1infotreesyncand always fetches a fresh leaf→LER proof from the source's bridge service (GET /bridge/v1/claim-proof) when preparing a claim — there is no "staleness" special case, because the proof isnever cached in the first place.
SourceAwarePreparerdispatches every claimer between this and the existingL1-origin preparer based on each request's
source_network, not the claimer's destination.claimtx,claimer.IsClaimed, global index derivation) is now keyed bysource_networkinstead of always assuming L1 origin. The claim ABI is byte-identical across L1 and L2 v2 bridge contracts, so
claimAsset/claimMessagepacking is reused unchanged.0(L1) claimer is now a legal, fully-supported configuration — like every other claimer,it has no
l2gersync, and additionally it is the only claimer type with no GER-injection gate at all (the GERalready exists in the L1 GER manager by construction once
l1infotreesynchas the leaf).origin_network:destination_network:deposit_count(origin = bridged token'sorigin, which isn't unique across multiple source networks) to
source_network:destination_network:deposit_count(the network the bridge exit was initiated on — this is also what the claim global index encodes).
[AutoClaim.L2ToLxBridgeDetector]is no longer a permanently-disabled placeholder — it is areal, independently-enableable detector. A new
[AutoClaim.BridgeServiceFinder]section (embedsbridgeservicefinder.Config) is required whenL2ToLxBridgeDetector.Enabled = trueor when any enabledclaimer has an L2 destination (
NetworkID != 0), in either direction — this includes a pure L1→L2 setup withthe L2ToLx detector disabled:
Validate()now enforcesRollupManagerAddrwhenever any enabled claimer targetsan L2 destination, not just when the detector is on.
[[AutoClaim.Claimers]]now acceptsNetworkID = 0as avalid L1-destination claimer (requires the L2ToLx detector to be reachable).
[[AutoClaim.Claimers]].BlockFinalityand.InitialBlockNumare removed — theyconfigured the now-deleted per-claimer
l2gersync. Autoclaim no longer consumes[L2GERSync]/[ReorgDetectorL2]at all (those sections remain, unchanged, for the node-globall2gersyncthat backs thebridge service and Aggoracle).
[AutoClaim.BridgeServiceFinder]to be configured(with
RollupManagerAddr) and the destination network's own bridge service to be reachable, whenever thedestination is an L2. Previously an L1→L2 claimer only needed the shared
[L2GERSync]/[ReorgDetectorL2]settings and its own RPC client; there was no dependency on a remote bridge service for GER readiness.
GET /bridge/v1/claim-candidatesno longer returns a per-bridge Merkle proof — the response is{"claim_candidates": [{"bridge": {...}}], "count": N}(previously each candidate also carriedproof_local_exit_rootandlocal_exit_root).to_ler/from_lerstill define the deposit-count range asrequest parameters; they are unaffected.
GET /bridge/v1/injected-l1-info-leafnow returns404 Not Found(previously500 Internal Server Error) when no injected global exit root covers the requested L1 info tree index yet. The Go client(
bridgeservice/client.Client.GetInjectedL1InfoLeaf) surfaces this asclient.ErrNotFoundinstead of a genericwrapped error. Existing e2e retry loops already treated any error from this call as "not ready, retry" and are
unaffected.
GET /autoclaim/v1/bridges/{id}) changed format fromorigin_network:destination_network:deposit_counttosource_network:destination_network:deposit_count. Forevery pre-existing L1→L2 request these are numerically identical (
source_networkis always0for L1→L2,same as
origin_networkwas for L1-origin tokens) — but any old client that assumed "origin network" semanticsfor a wrapped token bridged L1→L2 would have seen a different (bug-fixed) key than before; see Migration.
autoclaim_requestno longer has aleaf_proof_jsoncolumn (never shipped — see Migration).Any external tooling reading the SQLite database directly must drop references to it;
lerandverify_block_numare unaffected.claimAsset/claimMessagecalldata or the admin approve/reject endpoints.📋 Config Updates
Full reference:
docs/autoclaim.md(Configuration section, "Top-level keys" and "Claimer keys" tables).[AutoClaim.L2ToLxBridgeDetector].DisabledBridgeDetectorplaceholder is gone — both directions are independentlyenabled/disabled the same way (
Enabled = true/false).✅ Testing
Unit tests (
make test-unit, full repo green, exit 0, zero failures anywhere), coverage for every package thisplan touched or added:
autoclaim/apiautoclaim/apitypesautoclaim/bridgedetectorautoclaim/claimerautoclaim/claimtxautoclaim/configautoclaim/policyautoclaim/proofautoclaim/runtimeautoclaim/senderautoclaim/simulatorautoclaim/storageautoclaim/storage/migrationsautoclaim/typesbridgeservicebridgeservice/clientbridgesyncbridgesync/migrationsbridgesync/typesl1infotreesyncconfigmake build,make lint(in-scope packages clean — see Notes),make generate-mocks, andmake generate-swagger-docsall run clean with no resulting uncommitted diff.op-ppenvironment, one full green run (go test -v -run 'TestAutoClaimL1ToL2AllowAll|TestAutoClaimL1ToL2APIApprove|TestAutoClaimL1ToL2BasicFilter|TestAutoClaimL2ToL1AllowAll' -timeout 30m -count=1 ./test/e2e, exit 0, 300.06s total):TestAutoClaimL1ToL2AllowAll— PASS (53.28s) — L1→L2 regression, now exercising the destination-bridge-serviceGER gate instead of the old per-claimer
l2gersync.TestAutoClaimL1ToL2APIApprove— PASS (55.64s) — L1→L2 regression, manual approval flow.TestAutoClaimL1ToL2BasicFilter— PASS (54.22s) — L1→L2 regression, gas-simulation policy.TestAutoClaimL2ToL1AllowAll— PASS (80.04s) — new L2→L1 direction:L2ToLxdetector,RollupPreparer, anNetworkID = 0(L1-destination) claimer, end to end against a pessimistic-proofrollup (
VerifyPessimisticStateTransitionLER indexing exercised for real).op-pp-2chainsenvironment (AGGKIT_E2E_ENV=op-pp-2chains),TestAutoClaimL2ToL2AllowAllruntwice consecutively with
-count=1(per this repo's Go test-result caching):okin 286.13s.okin 285.27s.bridgeservicefinderresolving both source and destination networks, thedestination-bridge-service GER-injection gate, and the claim-time leaf-proof fetch path end to end.
git status/docker psclean.🐞 Issues
🔗 Related PRs
feat/bridge-service-finder, the head branch of feat: add bridgeservicefinder package #1692(
feat: add bridgeservicefinder package) — to be retargeted todeveloponce feat: add bridgeservicefinder package #1692 merges.feat: add op-pp-2chains env and L2→L2 bridge test) via commitc3e4e0f2— see the draft/gate note at the top of this description.
feat: autoclaim plan, the original L1→L2 Auto Claim implementation).📝 Notes
autoclaim/storage/migrations/autoclaim0002.sqladdssource_networktoautoclaim_request(existing rows default to
0, correct since they're all L1-origin), replaces theUNIQUE(origin_network, destination_network, deposit_count)constraint withUNIQUE(source_network, destination_network, deposit_count)(the real claim identity — it's what the global index encodes), re-keys every pre-existingautoclaim0001row'srequest_keyto the newsource:destination:deposit_countformat (a no-op value changefor existing rows, since
source_network = origin_network = 0for all of them), addsler/verify_block_numcolumns, and adds a new
autoclaim_ler_cursortable. This re-key is safe:autoclaim0001(introduced by feat: autoclaim plan #1662, merged this week) has not shipped in any tagged release or release branch — confirmed
against
git tag/release/*branches before implementing. The same unshipped migration briefly added, thenhad removed in place, a
leaf_proof_jsoncolumn: once the leaf→LER proof became always-fetch-at-claim-time(never stored), the column was dropped from the migration file directly rather than via a follow-up migration,
since it had never shipped either.
docs/autoclaim.md,docs/bridge_service.md, anddocs/common_config.mdare fully rewritten/updatedfor all three directions and both proof-pipeline changes (architecture diagrams, sequence diagrams, config
reference, API fields, storage tables, and the e2e test catalog) and were spot-checked against the final code
state as part of this PR's validation sweep — including the destination-bridge-service GER gate and the
claim-time-only leaf proof added after the initial L2→Lx docs pass.
GET /autoclaim/v1/bridgesgained asource_network(uint32) response field and list-filter queryparam, and a
ler(hex string, empty for L1-origin requests) response field.lerrecords the source LERobserved at detection time (used to pick the covering L1 info tree leaf); the leaf→LER proof itself is never
part of the stored request or the API response, since it is now always fetched fresh at claim time. Request IDs
are
source_network:destination_network:deposit_count(see Breaking Changes).make lintstill exits non-zero for the repo as a whole, but every finding is pre-existing baselinedebt outside this plan's footprint — verified per-line via
git blamethat each finding predates this plan'swork (either from before this branch existed, or from PR feat: add op-pp-2chains env and L2→L2 bridge test #1691's own commit history brought in by the
c3e4e0f2merge). Zero lint findings inautoclaim/**,bridgesync/**,bridgeservice/**,l1infotreesync/**, or the touched parts ofconfig/**/cmd/run.go.test-unit: two previously-flaky, pre-existing, out-of-scope tests (agglayer'sTestRateLimitWrapper_*timing assertions andclaimsync's docker-connectivity tests, both unrelated toautoclaim, flagged as such earlier in this plan's own execution log) were green on the final validation run —
full repo
make test-unitpassed with zero failures.(
025bbb2d) followed by the PR feat: add op-pp-2chains env and L2→L2 bridge test #1691 merge (c3e4e0f2, untouched — including its own commit history — per anexplicit decision not to rewrite anything brought in from that merge or upstream of it) and a small number of
focused commits on top (a pre-existing env config fix, the L2→L2 test itself, a mechanical lint fix, the
destination-bridge-service GER gate + claim-time-proof follow-up work, and a docs drift fix).
025bbb2d's "wip:"label was deliberately left as-is rather than split/reworded, since either change would require recreating the
c3e4e0f2merge commit, which was out of scope to touch.