feat(e2e): add two-chain anvil environment - #1802
Open
arnaubennassar wants to merge 4 commits into
Open
Conversation
… assumptions
Implements T3's design (aggkit-env-design.md): a new test/e2e/envs/anvil-2chains/
env built from the kurtosis-cdk anvil devnet snapshot (commit fc160450, K8's
GHCR-published digest-pinned images for anvil-001/l2-anvil-001/l2-anvil-002/
agglayer; aggkit-001/aggkit-002/aggkit-proxy-001 on aggkit:local). Two anvil L2
sovereign chains settle PessimisticProof certificates (TriggerCertMode=ASAP,
explicit in both configs) against one anvil L1, each aggkit running
aggsender+aggoracle+bridge+autoclaim, fronted by a shared aggkit-proxy.
Config tree copied from the actual published-run images' baked config (not a
separate local re-run capture, whose block numbers didn't match the digest-
pinned anvil chain state and caused "no contract code at given address" against
the RollupManager at a stale genesis block), then renamed per
extract-state.sh's documented kurtosis->aggkit path mapping.
agglayer carries a real TCP-connect healthcheck against its gRPC port (K5c's
fix); aggkit-00X/aggkit-proxy-001 gate on service_healthy against it and carry
no healthcheck of their own (distroless image, confirmed no shell). This avoids
the aggsender claim-syncer deadlock a `service_started` dependency reproducibly
hits.
Four Go edits generalize hardcoded op-pp-2chains assumptions so this new env
(and any future multi-chain env) works without a new env-name branch:
- loader.go: add EnvAnvil2Chains; load L2B based on summary.json key presence
instead of env name; add Env.ComposeServices(ctx) (docker compose config
--services) for log collection.
- checks.go: extend the L2A chain-ID check's env-name condition (kept
name-keyed on purpose -- it exists to catch a stale/wrong summary.json).
- testmain_test.go: dumpContainerLogs now iterates ComposeServices(ctx) instead
of a hardcoded, already under-covering service list.
Also fixes a real race in bridge_utils.go's BridgeL1ToL2: this env runs
AutoClaim's L1ToL2BridgeDetector on the same network TestMain's post-test
bridge check manually claims into, so the manual ClaimAsset call can lose the
race and revert with AlreadyClaimed. Check IsClaimed first (same check
autoclaim_test.go already uses) and treat an already-claimed deposit as
success instead of a failure.
anvil-2chains's aggkit-001 also sets user: "${UID}:${GID}" (matching op-pp's
own /tmp-bind-mount precedent) so files written to the host-mounted
aggkit-001-data dir stay host-owned and removable by the next run.
Verified: AGGKIT_E2E_ENV=anvil-2chains make test-e2e TEST_RUN='TestZZZNoSuchTest'
passes green from a clean docker state (43s wall clock), including both
post-test L1->L2 and L2->L1 bridge flows; op-pp and op-pp-2chains pass
unchanged with the same pattern (61.6s, 138.7s respectively); make lint and
gofmt are clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Q5xUjjWCiNQdm7cmeWYrs
A3: sweep and measure config knobs for test/e2e/envs/anvil-2chains against
the TestMain-only wall-clock (AGGKIT_E2E_ENV=anvil-2chains make test-e2e
TEST_RUN='TestZZZNoSuchTest'), 27 timed runs across 7 configurations.
- config/agglayer/config.toml: add
[settlement.pessimistic-proof-tx-config.retry-on-not-included-on-l1]
initial-interval = "5s" (was unset/60s default). Confirmed via container
log (sleep_duration dropped from ~62s to ~9-14s) and via
`agglayer validate-config` dump. Real isolated effect on this measured
command is smaller than the design doc expected (~6s), because the
measured post-test bridge check reads state through aggkit's own
L1InfoTreeSync (watches L1 directly), not through agglayer's internal
settlement-confirmation bookkeeping -- kept anyway since it is free
(no reorg-safety cost) and matters for any future test that reads
agglayer's own certificate-status API instead.
- config/{001,002}/aggkit-config.toml: AggOracle.WaitPeriodNextGER and
AggOracle.EVMSender.WaitPeriodMonitorTx lowered 10s -> 1s. This, not the
settlement retry interval, is the dominant lever found for this env:
it collapsed a reproducible ~36s/~43s bimodal split (poll-cycle alignment
noise on the L1->L2 GER-injection wait) into a tight ~35-37s cluster.
WaitPeriodMonitorTx=1s restores config/default.go's own upstream default;
WaitPeriodNextGER=1s is a deliberate deviation below its 10s default,
justified since it is a pure poll-frequency knob with no finality/
reorg-safety semantics.
Measured median: 43.18s (pre-tuning) -> 35.58s (chosen config), verified
green 3 consecutive times from clean docker in two independent batches.
settlement-policy=LatestBlock was tested and NOT shipped (no measured
benefit over SafeBlock at this scale). The MinimumNewCertificateInterval
default discrepancy (aggsender/config/config.go's dead 1h Go-struct
fallback vs config/default.go's 5m vs the env comment's incorrect 5s claim)
is resolved in writing with evidence: the real effective default is 5m,
confirmed via `aggkit run --save-config-path` dump
(Duration = 300000000000ns), and does not gate this measured command since
only one certificate per network is ever required by it.
Full measurement table, raw run logs, and effective-config dumps in
plans/snapshot-v2-aggkit-e2e/A3-evidence/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Q5xUjjWCiNQdm7cmeWYrs
This was referenced Aug 14, 2026
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
anvil-2chains, a hermetic two-L2 Anvil E2E environment assembled from the published snapshot bundle.AGGKIT_E2E_ENV=anvil-2chainsenvironment.📋 Config Updates
001and002, with Anvil L1/L2 RPC endpoints, per-chain keys and bridge addresses, andAggSender.CheckStatusCertificateInterval = "1s".TriggerCertMode = "ASAP"explicitly. This avoids theAutofallback path and makes certificate triggering independent of epoch-based timing.[settlement.pessimistic-proof-tx-config],confirmations = 1,settlement-policy = "SafeBlock", andretry-on-not-included-on-l1.initial-interval = "5s". The short retry rechecks an already-broadcast settlement transaction sooner;SafeBlockwas retained becauseLatestBlockshowed no measured benefit.WaitPeriodNextGER = "1s"andWaitPeriodMonitorTx = "1s". The former was the largest measured lever for the L1→L2 GER-injection wait.0.6.0-rc.8; existingop-pp*environments remain on0.4.4. The configurations are intentionally not treated as like-for-like performance baselines.✅ Testing
🤖 Automatic: A3 local measurement (
go clean -testcache && AGGKIT_E2E_ENV=anvil-2chains make test-e2e TEST_RUN='TestZZZNoSuchTest') measured the following median E2E lifecycle times:The meaningful same-environment comparison is C0 → tuned: 43.177s → 35.579s. The op-pp comparison differs in both chain software and Agglayer version (
0.4.4vs0.6.0-rc.8). Full measurement evidence:plans/snapshot-v2-aggkit-e2e/A3-evidence/MEASUREMENTS.md.🤖 Automatic: Go E2E Tests run 31802147383 completed successfully at
0a6481cc. The dedicatedanvil-2chains / defaultjob ran (not skipped) in 6m32s; itsRun E2E testsstep ran in 68s. All five existing legacy matrix entries ran (not skipped) and passed:op-pp / default(18m58s),op-pp / removeger-fast(7m28s),op-pp / removeger-b1(9m45s),op-pp / removeger-b2(10m58s), andop-pp-2chains / default(16m23s). The separateforce_ger_updatejob also ran (not skipped) and passed in 5m21s.🖱️ Manual: Verified locally from clean Docker state as part of the A3 tuning sweep, including the environment’s
TestMainL1↔L2 bridge health check and teardown.🐞 Issues
🔗 Related PRs
sdk#28) remaining its prerequisite.📝 Notes