feat: add op-pp-2chains env and L2→L2 bridge test#1691
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>
|
Claude finished @arnaubennassar's task in 2m 21s —— View job PR Review CompleteReviewed PR #1691: feat(e2e): add op-pp-2chains env and L2→L2 bridge test 🚀 What's NewThis PR introduces a comprehensive two-network OP-PP e2e testing environment that enables L2→L2 bridge testing. It adds the 🐛 Bug FixesTwo critical claimsync bugs were discovered and fixed during testing:
📋 Config UpdatesNone.
|
- 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>
…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
|



🔄 Changes Summary
test/e2e/envs/op-pp-2chains) generated from a kurtosis-cdk snapshot, running two OP-PP L2 chains (chain IDs 20201/20202) on a shared L1 and agglayer instance.L2B(second L2) alongsideL2(first L2/L2A) without breaking the existing single-chainop-ppenv.BridgeL2ToL2helper inbridge_utils.gothat bridges a MintableERC20 token L2A → L2B and asserts the destination ERC20 balance after claim.TestBridgeL2ToL2exercising the full L2→L2 bridge flow end-to-end.test-go-e2e.yml) over[op-pp, op-pp-2chains]withfail-fast: false; bumps job timeout to 60 min for GER-propagation headroom.BridgeL1ToL2/BridgeL1ToL2WithResultto use the actual injectedL1InfoTreeIndexfromGetInjectedL1InfoLeaffor claim proofs (the API returns the first GER injected with index ≥ requested; using a stale lower index produced a GER hash absent from L2'sGlobalExitRootMap, causingClaimAssetto revert).claimsyncbugs: concurrent block-0 insert race inL2ClaimSyncerstartup and incorrect finality parameter in the EVMDownloader safe-zone check.📋 Config Updates
✅ Testing
TestBridgeL2ToL2added and passing in CI underAGGKIT_E2E_ENV=op-pp-2chains. Bothop-ppandop-pp-2chainsmatrix jobs pass.AGGKIT_E2E_ENV=op-pp-2chains go test -v -timeout 30m -run TestBridgeL2ToL2 ./test/e2e/passes from a cold Docker start (~250 s total including post-test L1↔L2 health checks).🐞 Issues
🔗 Related PRs
feat/op-pp-2chains-snapshot@627340aa📝 Notes
op-pp-2chainsenv uses kurtosis-generated images pushed to thearnaubennassarDocker Hub namespace (arnaubennassar/geth:op-pp-2chains,arnaubennassar/beacon:op-pp-2chains,arnaubennassar/validator:op-pp-2chains).aggkit:localis built from source in CI.govulncheckfailure is pre-existing ondevelop(not introduced by this PR).