diff --git a/.github/workflows/test-e2e-multi-chains.yml b/.github/workflows/test-e2e-multi-chains.yml new file mode 100644 index 000000000..af341d56d --- /dev/null +++ b/.github/workflows/test-e2e-multi-chains.yml @@ -0,0 +1,240 @@ +name: Aggkit E2E - Multi Chain + +on: + workflow_call: + inputs: + aggsender-find-imported-bridge-artifact: + description: "Artifact containing the aggsender imported-bridge helper" + required: true + type: string + kurtosis-cdk-ref: + description: "The kurtosis-cdk revision to test" + required: true + type: string + agglayer-e2e-ref: + description: "The agglayer/e2e revision containing the Bats tests" + required: true + type: string + kurtosis-cdk-args-1: + description: "Kurtosis arguments for the first chain" + required: true + type: string + kurtosis-cdk-args-2: + description: "Kurtosis arguments for the second chain" + required: true + type: string + kurtosis-cdk-args-3: + description: "Kurtosis arguments for the optional third chain" + required: false + type: string + default: "{}" + kurtosis-cdk-enclave-name: + description: "Kurtosis enclave name" + required: true + type: string + docker-image-override: + description: "Kurtosis image key to override" + required: false + type: string + default: "" + docker-tag: + description: "Local Docker tag used by Kurtosis" + required: false + type: string + default: "local" + docker-artifact-name: + description: "Artifact containing the Docker image" + required: false + type: string + default: "aggkit" + number-of-chains: + description: "Number of chains to deploy" + required: false + type: number + default: 2 + aggkit-config-artifact: + description: "Artifact containing aggkit-owned Kurtosis config templates" + required: false + type: string + default: "" + +permissions: + contents: read + packages: write + id-token: write + +jobs: + test-multi-aggkit-e2e: + name: Multi chains E2E test + runs-on: ubuntu-latest + steps: + - name: Checkout agglayer-e2e + uses: actions/checkout@v6 + with: + repository: agglayer/e2e + ref: ${{ inputs.agglayer-e2e-ref }} + path: agglayer-e2e + + - name: Checkout kurtosis-cdk + uses: actions/checkout@v6 + with: + repository: 0xPolygon/kurtosis-cdk + ref: ${{ inputs.kurtosis-cdk-ref }} + path: kurtosis-cdk + + - name: Download binary + if: inputs.aggsender-find-imported-bridge-artifact != '' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.aggsender-find-imported-bridge-artifact }} + path: /tmp + + - name: Download artifact + if: inputs.docker-artifact-name != '' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.docker-artifact-name }} + path: /tmp + + - name: Load image + run: | + if [[ -e /tmp/${{ inputs.docker-artifact-name }}.tar ]]; then + docker load --input /tmp/${{ inputs.docker-artifact-name }}.tar + docker images + fi + + # foundry-toolchain downloads foundryup from foundry.paradigm.xyz. The + # endpoint intermittently returns a non-shell executable on hosted + # runners, causing every retry to fail before the Bats tests start. + - name: Install Foundry + env: + FOUNDRY_VERSION: v1.7.0 + FOUNDRY_SHA256: 88501301c43e2cb3231009e68bd76af17cc0f7e9981f9d37ceabc6b857febb2f + run: | + archive="foundry_${FOUNDRY_VERSION}_linux_amd64.tar.gz" + install_dir="${RUNNER_TEMP}/foundry/bin" + mkdir -p "${install_dir}" + curl -fsSLO "https://github.com/foundry-rs/foundry/releases/download/${FOUNDRY_VERSION}/${archive}" + echo "${FOUNDRY_SHA256} ${archive}" | sha256sum --check + tar -xzf "${archive}" -C "${install_dir}" + "${install_dir}/cast" --version + echo "${install_dir}" >> "${GITHUB_PATH}" + + - name: Install bats + uses: bats-core/bats-action@3.0.0 + + - name: Install kurtosis cli and jq + run: | + echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list + sudo apt update + sudo apt install -y kurtosis-cli jq + kurtosis analytics disable + + - name: Write args input to a file + run: | + if [[ '${{ inputs.kurtosis-cdk-args-3 }}' == '{}' && ${{ inputs.number-of-chains }} -eq 3 ]]; then + echo "Error: kurtosis-cdk-args-3 is empty but number-of-chains is set to 3" + exit 1 + fi + docker_tag='${{ inputs.docker-tag }}' + if [[ "${docker_tag}" == "" ]]; then + echo '${{ inputs.kurtosis-cdk-args-1 }}' | tee /tmp/kurtosis-args-1.json + echo '${{ inputs.kurtosis-cdk-args-2 }}' | tee /tmp/kurtosis-args-2.json + if [[ ${{ inputs.number-of-chains }} -eq 3 ]]; then + echo '${{ inputs.kurtosis-cdk-args-3 }}' | tee /tmp/kurtosis-args-3.json + fi + exit + fi + echo '${{ inputs.kurtosis-cdk-args-1 }}' | jq --arg img '${{ inputs.docker-image-override }}' \ + --arg tag '${{ inputs.docker-tag }}' '.args[$img] = $tag' | tee /tmp/kurtosis-args-1.json + echo '${{ inputs.kurtosis-cdk-args-2 }}' | jq --arg img '${{ inputs.docker-image-override }}' \ + --arg tag '${{ inputs.docker-tag }}' '.args[$img] = $tag' | tee /tmp/kurtosis-args-2.json + if [[ ${{ inputs.number-of-chains }} -eq 3 ]]; then + echo '${{ inputs.kurtosis-cdk-args-3 }}' | jq --arg img '${{ inputs.docker-image-override }}' \ + --arg tag '${{ inputs.docker-tag }}' '.args[$img] = $tag' | tee /tmp/kurtosis-args-3.json + fi + + - name: Download aggkit config override + if: inputs.aggkit-config-artifact != '' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.aggkit-config-artifact }} + path: /tmp/aggkit-config + + - name: Apply aggkit config override + if: inputs.aggkit-config-artifact != '' + run: | + set -eo pipefail + dst=kurtosis-cdk/static_files/chain/shared/aggkit + cp /tmp/aggkit-config/aggkit-config.template.toml "${dst}/config.toml" + if [[ -f /tmp/aggkit-config/aggkit-cdk-config.template.toml ]]; then + cp /tmp/aggkit-config/aggkit-cdk-config.template.toml "${dst}/cdk-config.toml" + fi + echo "Applied aggkit-owned config templates over kurtosis-cdk static_files:" + ls -l "${dst}" + + - name: Startup the kurtosis-cdk package + run: | + pushd kurtosis-cdk || exit 1 + kurtosis run --enclave '${{ inputs.kurtosis-cdk-enclave-name }}' --args-file /tmp/kurtosis-args-1.json . + kurtosis run --enclave '${{ inputs.kurtosis-cdk-enclave-name }}' --args-file /tmp/kurtosis-args-2.json . + if [[ ${{ inputs.number-of-chains }} -eq 3 ]]; then + kurtosis run --enclave '${{ inputs.kurtosis-cdk-enclave-name }}' --args-file /tmp/kurtosis-args-3.json . + fi + popd + + - name: Run e2e tests + run: | + pushd agglayer-e2e || exit 1 + set -a + source ./tests/.env + set +a + export BATS_LIB_PATH="${PWD}/core/helpers/lib" + export PROJECT_ROOT="${PWD}" + export ENCLAVE_NAME='${{ inputs.kurtosis-cdk-enclave-name }}' + chmod +x "/tmp/${{ inputs.aggsender-find-imported-bridge-artifact }}" + export AGGSENDER_IMPORTED_BRIDGE_PATH="/tmp/${{ inputs.aggsender-find-imported-bridge-artifact }}" + if [[ ${{ inputs.number-of-chains }} -eq 3 ]]; then + bats ./tests/aggkit/bridge-e2e-3-chains.bats + else + bats ./tests/aggkit/bridge-e2e-2-chains.bats + fi + popd + + - name: Dump enclave logs + if: ${{ failure() }} + run: kurtosis dump ./dump + + - name: Generate archive name + if: ${{ failure() }} + run: | + archive_name="dump_run_with_args_${{ inputs.number-of-chains }}_${{ github.run_id }}" + echo "ARCHIVE_NAME=${archive_name}" >> "${GITHUB_ENV}" + echo "Generated archive name: ${archive_name}" + + - name: Upload logs + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARCHIVE_NAME }} + path: ./dump + + - name: Clean up kurtosis-cdk enclave + if: ${{ always() }} + run: | + kurtosis enclave stop '${{ inputs.kurtosis-cdk-enclave-name }}' + kurtosis clean + + - name: Record test run in Datadog + if: always() + continue-on-error: true + uses: agglayer/gha-record-e2e-test-run@v1 + with: + enable_ai_analysis: "true" + host: github-actions-runners + status: ${{ job.status }} + ref_kurtosis: ${{ inputs.kurtosis-cdk-ref }} + ref_e2e: ${{ inputs.agglayer-e2e-ref }} + env: + DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f5928e40a..2ca8bc419 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -273,7 +273,7 @@ jobs: - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-multi-chains.yml@e0dcceca73cc66c1dd577da2b491887586c0d4b8 + uses: ./.github/workflows/test-e2e-multi-chains.yml secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} @@ -301,7 +301,7 @@ jobs: - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-multi-chains.yml@e0dcceca73cc66c1dd577da2b491887586c0d4b8 + uses: ./.github/workflows/test-e2e-multi-chains.yml secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} diff --git a/.github/workflows/test-go-e2e.yml b/.github/workflows/test-go-e2e.yml index 0a0bed5e0..b84a3d9e7 100644 --- a/.github/workflows/test-go-e2e.yml +++ b/.github/workflows/test-go-e2e.yml @@ -43,22 +43,16 @@ jobs: - name: Pull Docker images from compose files run: | - # op-pp env - cd test/e2e/envs/op-pp - docker compose pull geth beacon validator agglayer op-geth-001 op-node-001 - cd ../op-pp-2chains - # kurtosis-cdk snapshot branch: feat/op-pp-2chains-snapshot @ 627340aa - # This commit generated the op-pp-2chains env snapshot - docker compose pull geth beacon validator agglayer \ - op-reth-001 op-node-001 op-reth-002 op-node-002 + cd test/e2e/envs/anvil-2chains + # Digest-pinned kurtosis-cdk anvil snapshot images. The aggkit services + # deliberately use the locally built aggkit:local image and are excluded. + docker compose pull anvil-001 l2-anvil-001 l2-anvil-002 agglayer - name: Save pulled Docker images run: | - imgs="" - for d in op-pp op-pp-2chains; do - imgs="$imgs $(cd test/e2e/envs/$d && docker compose config --images | grep -v 'aggkit:local')" - done - docker save $(echo "$imgs" | tr ' ' '\n' | sort -u) -o /tmp/docker-images.tar + mapfile -t imgs < <(cd test/e2e/envs/anvil-2chains && \ + docker compose config --images | grep -v 'aggkit:local' | sort -u) + docker save "${imgs[@]}" -o /tmp/docker-images.tar - name: Upload Docker images artifact uses: actions/upload-artifact@v4 @@ -76,29 +70,32 @@ jobs: fail-fast: false matrix: include: - # Default op-pp group: everything except the remove-GER tests, which run in their own - # groups below (each matrix entry is a separate `go test` invocation, so listing a test in - # more than one entry's regex would run it twice). Enumerated as an explicit positive, - # anchored regex because Go's -run has no negation syntax. - - env: op-pp - group: default - run: "^(TestJustBridge|TestAutoClaimL1ToL2AllowAll|TestAutoClaimL1ToL2APIApprove|TestAutoClaimL1ToL2BasicFilter|TestAutoClaimL2ToL1AllowAll|TestAutoClaimL2ToL2AllowAll|TestBridgeL2ToL2|TestBackwardForwardLET_NoDivergence|TestBackwardForwardLET_Case1|TestBackwardForwardLET_Case2|TestBackwardForwardLET_Case3|TestBackwardForwardLET_Case4|TestBackwardForwardLET_AggsenderAPIFallback)$" - # Remove-GER groups (op-pp): split across 3 CI matrix entries under the 20-min-per-group - # budget. Measured passing-path wall-clock (S8/S9 reports) is well under this budget for - # every group (all five tests combined finish in under 6 minutes single-run), so no further - # regrouping is needed. - - env: op-pp + # Keep stateful suites in separate stacks. The AutoClaim and backward/forward LET tests + # restart services and mutate chain/AggLayer state, so sharing one go-test process makes + # a primary failure cascade into otherwise unrelated bridge tests. + - env: anvil-2chains + group: autoclaim + run: "^(TestAutoClaimL1ToL2AllowAll|TestAutoClaimL1ToL2APIApprove|TestAutoClaimL1ToL2BasicFilter|TestAutoClaimL2ToL1AllowAll|TestAutoClaimL2ToL2AllowAll)$" + - env: anvil-2chains + group: bridge + run: "^(TestJustBridge|TestBridgeL2ToL2|TestBridgeTrackerL1ToL2)$" + - env: anvil-2chains + group: backward-forward-let + run: "^(TestBackwardForwardLET_NoDivergence|TestBackwardForwardLET_Case1|TestBackwardForwardLET_Case2|TestBackwardForwardLET_Case3|TestBackwardForwardLET_Case4|TestBackwardForwardLET_AggsenderAPIFallback)$" + # Remove-GER scenarios mutate claim bitmaps and emergency state. Category A has a fixed + # deposit-count-2 proof and must not share a snapshot with a preceding bridge test. + - env: anvil-2chains group: removeger-fast - run: TestRemoveGER_NoProblematicClaims|TestRemoveGER_CategoryA|TestGenerateInvalidGER - - env: op-pp + run: "^(TestRemoveGER_NoProblematicClaims|TestGenerateInvalidGER)$" + - env: anvil-2chains + group: removeger-category-a + run: "^TestRemoveGER_CategoryA$" + - env: anvil-2chains group: removeger-b1 run: TestRemoveGER_CategoryB1 - - env: op-pp + - env: anvil-2chains group: removeger-b2 run: TestRemoveGER_CategoryB2 - - env: op-pp-2chains - group: default - run: TestBridgeL2ToL2|TestAutoClaimL2ToL2AllowAll|TestBridgeTrackerL1ToL2 steps: - name: Checkout code uses: actions/checkout@v5 @@ -113,6 +110,21 @@ jobs: sudo apt-get update sudo apt-get install -y docker-compose + - name: Install cast + if: matrix.group == 'removeger-fast' + env: + FOUNDRY_VERSION: v1.8.1 + run: | + archive="foundry_${FOUNDRY_VERSION}_linux_amd64.tar.gz" + install_dir="${RUNNER_TEMP}/foundry/bin" + mkdir -p "${install_dir}" + curl -fsSLO "https://github.com/foundry-rs/foundry/releases/download/${FOUNDRY_VERSION}/${archive}" + curl -fsSLO "https://github.com/foundry-rs/foundry/releases/download/${FOUNDRY_VERSION}/${archive%.tar.gz}.sha256" + sha256sum --check "${archive%.tar.gz}.sha256" + tar -xzf "${archive}" -C "${install_dir}" cast + "${install_dir}/cast" --version + echo "${install_dir}" >> "${GITHUB_PATH}" + - name: Download aggkit Docker image uses: actions/download-artifact@v4 with: @@ -139,7 +151,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - # Include matrix.group: multiple matrix entries now share the same env (op-pp), and + # Include matrix.group because multiple matrix entries share the same Anvil env, and # actions/upload-artifact@v4 requires unique artifact names within a workflow run. name: e2e-test-results-${{ matrix.env }}-${{ matrix.group }} path: | @@ -184,6 +196,8 @@ jobs: docker load -i /tmp/docker-images.tar - name: Run force_ger_update E2E test + env: + AGGKIT_E2E_ENV: anvil-2chains run: make test-e2e-force_ger_update - name: Upload test results diff --git a/Makefile b/Makefile index 4782aa69a..b068d2936 100644 --- a/Makefile +++ b/Makefile @@ -154,14 +154,13 @@ test-unit: ## Runs the unit tests TEST_RUN ?= .PHONY: test-e2e test-e2e: ## Runs the e2e tests - # 60m: covers the CI matrix's remove-GER groups (test-go-e2e.yml), whose combined per-test context - # budgets can exceed the previous 45m; kept in step with job timeout-minutes: 60 there. Provisional - # -- S8/S9 tighten per-test timeouts and may lower this once real durations are measured. + # Keep this aligned with test-go-e2e.yml's job timeout; remove-GER scenarios have long retry budgets. go test -v -timeout 60m $(if $(TEST_RUN),-run "$(TEST_RUN)") ./test/e2e/... .PHONY: test-e2e-force_ger_update test-e2e-force_ger_update: ## Runs the isolated force_ger_update e2e test (dedicated CI job/runner only) - RUN_FORCE_GER_UPDATE_E2E=true E2E_SKIP_POSTTEST_BRIDGE_CHECK=true go test -v -timeout 30m -run TestForceGERUpdateE2E ./test/e2e/... + AGGKIT_E2E_ENV=anvil-2chains RUN_FORCE_GER_UPDATE_E2E=true E2E_SKIP_POSTTEST_BRIDGE_CHECK=true \ + go test -v -timeout 30m -run TestForceGERUpdateE2E ./test/e2e/... .PHONY: lint lint: ## Runs the linter diff --git a/docs/autoclaim.md b/docs/autoclaim.md index f2a995023..76a49dfa7 100644 --- a/docs/autoclaim.md +++ b/docs/autoclaim.md @@ -700,8 +700,8 @@ make lint make test-unit ``` -The focused end-to-end tests run against the dockerized e2e environment (see [End-to-end tests](./e2e_tests.md)). -L1-to-L2 and L2-to-L1 run against the single-chain `op-pp` environment (the default): +The focused end-to-end tests run against the two-chain `anvil-2chains` environment by default (see +[End-to-end tests](./e2e_tests.md)): ```bash go test -v -run 'TestAutoClaimL1ToL2(AllowAll|APIApprove|BasicFilter)|TestAutoClaimL2ToL1AllowAll' -timeout 30m ./test/e2e @@ -713,10 +713,10 @@ go test -v -run 'TestAutoClaimL1ToL2(AllowAll|APIApprove|BasicFilter)|TestAutoCl `TestAutoClaimL2ToL1AllowAll` exercises the fully automatic L2-to-L1 flow (L2-to-Lx detector, `RollupPreparer`, an `NetworkID = 0` claimer). -L2-to-L2 requires the two-rollup `op-pp-2chains` environment, selected via `AGGKIT_E2E_ENV`: +L2-to-L2 uses the same default environment: ```bash -AGGKIT_E2E_ENV=op-pp-2chains go test -v -run 'TestAutoClaimL2ToL2AllowAll' -timeout 30m ./test/e2e +go test -v -run 'TestAutoClaimL2ToL2AllowAll' -timeout 30m ./test/e2e ``` `TestAutoClaimL2ToL2AllowAll` exercises the fully automatic L2-to-L2 flow end to end: the L2-to-Lx detector and diff --git a/docs/e2e_tests.md b/docs/e2e_tests.md index 793bb8958..c249a7561 100644 --- a/docs/e2e_tests.md +++ b/docs/e2e_tests.md @@ -51,7 +51,7 @@ kills `docker compose up` (`signal: killed`) before the tests start, rerun the c ### Remove GER (invalid-GER recovery) -Exercises the [remove-GER runbook](./remove_ger_runbook.md) end to end against the `op-pp` env: inject an invalid +Exercises the [remove-GER runbook](./remove_ger_runbook.md) end to end against the `anvil-2chains` env: inject an invalid GER on L2, confirm l2gersync blocks on it, run the `remove_ger` tool's recovery flow (`freeze bridge -> removeGlobalExitRoots -> category-specific claim correction -> restore bridge`), and confirm l2gersync recovers automatically and resumes normal processing. Implemented in `test/e2e/removeger_test.go`: @@ -71,7 +71,7 @@ go test -v -run 'TestRemoveGER_(NoProblematicClaims|CategoryA|CategoryB1|Categor - `TestGenerateInvalidGER` — exercises the `remove_ger` tool's `generate` subcommand (which crafts and injects a synthetic invalid GER via `cast`) as a standalone check of the generation path. This test drives `cast send`/`cast call` from the **host** (outside Docker) against the L2 RPC port published by - the `op-pp` compose env; on a dev machine whose local foundry `cast` cannot open outbound connections to + the Anvil compose env; on a dev machine whose local foundry `cast` cannot open outbound connections to that Docker-published port (while the Go `ethclient` used elsewhere in the harness reaches it fine — a machine-local `cast` networking quirk, not an aggkit or test defect), the test detects this via a preflight probe and cleanly `t.Skip`s rather than failing. CI installs `cast` fresh and reaches the @@ -95,18 +95,17 @@ assertions. #### CI matrix -`.github/workflows/test-go-e2e.yml` runs the remove-GER tests on `op-pp` in three dedicated matrix groups, -each under the 20-minute per-job budget (measured passing-path wall-clock is well under 6 minutes for all -five tests combined, run back-to-back in a single env), so the `op-pp / default` group's regex explicitly -excludes them (Go's `-run` has no negation syntax, so the default group is enumerated as a positive, -anchored regex instead): +`.github/workflows/test-go-e2e.yml` runs the remove-GER tests on `anvil-2chains` in three dedicated matrix groups, +so each group gets an isolated compose stack and cannot leak mutated chain state into the default group. The +`anvil-2chains / default` group's regex explicitly excludes them (Go's `-run` has no negation syntax, so the +default group is enumerated as a positive, anchored regex instead): | Matrix group (`env` / `group`) | Tests | | --- | --- | -| `op-pp` / `removeger-fast` | `TestRemoveGER_NoProblematicClaims`, `TestRemoveGER_CategoryA`, `TestGenerateInvalidGER` | -| `op-pp` / `removeger-b1` | `TestRemoveGER_CategoryB1` | -| `op-pp` / `removeger-b2` | `TestRemoveGER_CategoryB2` | -| `op-pp` / `default` | Everything else on `op-pp` (positive-regex list, remove-GER tests excluded) | +| `anvil-2chains` / `removeger-fast` | `TestRemoveGER_NoProblematicClaims`, `TestRemoveGER_CategoryA`, `TestGenerateInvalidGER` | +| `anvil-2chains` / `removeger-b1` | `TestRemoveGER_CategoryB1` | +| `anvil-2chains` / `removeger-b2` | `TestRemoveGER_CategoryB2` | +| `anvil-2chains` / `default` | Everything else (positive-regex list, remove-GER tests excluded) | ## Two L2 networks diff --git a/test/e2e/autoclaim_test.go b/test/e2e/autoclaim_test.go index 6760175df..2da345823 100644 --- a/test/e2e/autoclaim_test.go +++ b/test/e2e/autoclaim_test.go @@ -30,43 +30,41 @@ import ( ) const ( - autoClaimAPIBaseURL = "http://127.0.0.1:11579" - bridgeServiceBaseURL = "http://127.0.0.1:11577" + autoClaimAPIBaseURL = "http://127.0.0.1:14579" + bridgeServiceBaseURL = "http://127.0.0.1:14577" autoClaimKeystorePass = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" autoClaimBridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" - autoClaimL2RPC = "http://op-geth-001:8545" - autoClaimL2ChainID = 2151908 + autoClaimL2RPC = "http://l2-anvil-001:8545" + autoClaimL2ChainID = 20201 autoClaimRequestWait = 8 * time.Minute autoClaimRestartWait = 2 * time.Minute autoClaimRestoreWait = 2 * time.Minute autoClaimBridgeAmountWei = 100000000000000 - // autoClaimL1BridgeAddr is the op-pp env's L1 bridge contract address (from - // test/e2e/envs/op-pp/summary.json: networks.l1.contracts.bridge). It happens to equal + // autoClaimL1BridgeAddr is the Anvil env's L1 bridge contract address. It equals // autoClaimBridgeAddr (the L2 bridge address) because this env deploys the bridge at the same // deterministic address on every network; kept as a separate constant for clarity at L2ToLx // claimer call sites. autoClaimL1BridgeAddr = autoClaimBridgeAddr - // autoClaimL1RPC is the in-network URL of the op-pp env's L1 geth node (docker-compose.yml's - // "geth" service, summary.json: networks.l1.services.geth.http_rpc.internal). - autoClaimL1RPC = "http://geth:8545" - // autoClaimL1ChainID is the op-pp env's L1 chain ID (summary.json: networks.l1.chain_id). + // autoClaimL1RPC is the in-network URL of the Anvil env's L1 node. + autoClaimL1RPC = "http://anvil-001:8545" + // autoClaimL1ChainID is the Anvil env's L1 chain ID. autoClaimL1ChainID = 271828 - // autoClaimSourceBridgeServiceURL is the in-network URL of the op-pp env's (only) L2 bridge + // autoClaimSourceBridgeServiceURL is the in-network URL of the Anvil env's primary L2 bridge // service, used as a static AutoClaim.BridgeServiceFinder.BridgeURLs override for the L2ToLx detector. // The on-chain fallback (trusted sequencer URL + port 5577) would resolve to the wrong host in // this docker-compose env, so a static override is required. autoClaimSourceBridgeServiceURL = "http://aggkit-001:5577" - // The following constants describe the 2-chain env (EnvOpPP2Chains) used by + // The following constants describe the two-chain Anvil env used by // TestAutoClaimL2ToL2AllowAll. In that env Auto Claim runs on the aggkit-002 (network 2 / L2B) // node: it detects the L2-001 -> L2-002 bridge via the L2ToLx detector, resolves the source // (network 1) bridge service statically, and claims on network 2 through a claimer with its own // l2gersync. // autoClaimL2BRPC is the in-network RPC URL of the 2-chain env's second L2 execution client - // (op-reth-002). It backs the network-2 claimer's tx sender and its l2gersync. - autoClaimL2BRPC = "http://op-reth-002:8545" + // (l2-anvil-002). It backs the network-2 claimer's tx sender and its l2gersync. + autoClaimL2BRPC = "http://l2-anvil-002:8545" // autoClaimL2BChainID is the 2-chain env's second L2 chain ID (summary.json: l2_networks.002.chain_id). autoClaimL2BChainID = 20202 // autoClaimNet1BridgeServiceURL / autoClaimNet2BridgeServiceURL are the in-network URLs of the two @@ -80,7 +78,7 @@ const ( autoClaimL1RollupManagerAddr = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" // autoClaimL2BBridgeServiceBaseURL is the external (host) URL of the network-2 bridge service, // which also serves the public Auto Claim request-status API (/autoclaim/v1/bridges/). - autoClaimL2BBridgeServiceBaseURL = "http://127.0.0.1:12577" + autoClaimL2BBridgeServiceBaseURL = "http://127.0.0.1:15577" // l2NetworkKeyB mirrors envs.l2NetworkKeyB (unexported): the summary.json key / config dir of the // secondary L2 network (L2B) whose aggkit node runs Auto Claim in the L2->L2 test. l2NetworkKeyB = "002" @@ -139,7 +137,7 @@ func testAutoClaimL1ToL2(t *testing.T, policyName string, approveThroughAPI bool ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer cancel() - env := loadAutoClaimTestEnv(t, ctx) + env := loadAutoClaimTestEnv(t) enableAutoClaimForTest(t, ctx, env, policyName, nil) waitForBridgeServiceSynced(ctx, t) @@ -186,7 +184,7 @@ func TestAutoClaimL2ToL1AllowAll(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 28*time.Minute) defer cancel() - env := loadAutoClaimTestEnv(t, ctx) + env := loadAutoClaimTestEnv(t) // The L1-destination claimer's EthTxManager needs a funded L1 signer; checked out here (before // enabling Auto Claim) because newAutoClaimL2ToLxConfig needs the key to provision the claimer's @@ -206,8 +204,8 @@ func TestAutoClaimL2ToL1AllowAll(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { env.Keys.L2Keys.Return(l2Key) }) - // This op-pp docker-compose env runs an OP-stack sequencer with NO op-batcher/op-proposer, so the - // L2 chain's safe/finalized heads never advance past genesis. The aggsender only certifies L2 + // The local snapshot env may not advance the L2 chain's safe/finalized heads. The aggsender only + // certifies L2 // blocks up to min(lastBridgeBlock, lastClaimBlock) (aggsender/query/bridge_query.go), and its L2 // claim syncer -- whose reorg-safe boundary is the (stuck-at-0) finalized head -- only advances // when it observes an L2 claim event. A pure L2->L1 bridge produces no L2 claim, so without @@ -361,8 +359,8 @@ func assertClaimedOnL1(ctx context.Context, t *testing.T, env *envs.Env, deposit ) } -// TestAutoClaimL2ToL2AllowAll proves the L2->L2 Auto Claim direction end to end on the 2-chain env -// (EnvOpPP2Chains, selected via AGGKIT_E2E_ENV=op-pp-2chains). Auto Claim runs on the network-2 +// TestAutoClaimL2ToL2AllowAll proves the L2->L2 Auto Claim direction end to end on the two-chain +// Anvil env. Auto Claim runs on the network-2 // (L2B) node: the L2ToLx detector observes network 1's local exit root settle to L1, fetches the // L2-001 -> L2-002 bridge from network 1's bridge service (static finder URL), and a network-2 // claimer with its own l2gersync waits for the GER covering that LER to be injected on network 2 @@ -512,14 +510,14 @@ func assertClaimedOnL2B(ctx context.Context, t *testing.T, env *envs.Env, deposi } // loadAutoClaimL2ToL2TestEnv returns the shared env loaded once by TestMain (selected via -// AGGKIT_E2E_ENV), skipping the test when it isn't the 2-chain env (EnvOpPP2Chains). Mirrors +// AGGKIT_E2E_ENV), skipping the test when it isn't a two-chain env. Mirrors // TestBridgeL2ToL2's pattern: a second, independent docker-compose stack must not be brought up // alongside the singleton env TestMain already started, since both envs bind the same host ports. func loadAutoClaimL2ToL2TestEnv(t *testing.T) *envs.Env { t.Helper() require.NotNil(t, testEnv, "testEnv must be set by TestMain") if testEnv.L2B == nil { - t.Skip("L2->L2 Auto Claim test requires EnvOpPP2Chains (L2B must be non-nil)") + t.Skip("L2->L2 Auto Claim test requires a multi-chain env (L2B must be non-nil)") } return testEnv } @@ -638,19 +636,10 @@ HTTPHeaders = {} ) } -func loadAutoClaimTestEnv(t *testing.T, ctx context.Context) *envs.Env { +func loadAutoClaimTestEnv(t *testing.T) *envs.Env { t.Helper() - loadCtx, loadCancel := context.WithTimeout(ctx, 5*time.Minute) - defer loadCancel() - env, err := envs.LoadEnv(loadCtx, envs.EnvOpPP) - require.NoError(t, err) - - checkCtx, checkCancel := context.WithTimeout(ctx, 5*time.Minute) - defer checkCancel() - require.NoError(t, env.CheckEnv(checkCtx)) - - testEnv = env - return env + require.NotNil(t, testEnv, "testEnv must be set by TestMain") + return testEnv } // enableAutoClaimForTest restarts aggkit with a patched Auto Claim config for the given policy. diff --git a/test/e2e/backwardforwardlet_test.go b/test/e2e/backwardforwardlet_test.go index c539571ef..0d77c796f 100644 --- a/test/e2e/backwardforwardlet_test.go +++ b/test/e2e/backwardforwardlet_test.go @@ -61,6 +61,7 @@ type summaryForBFLToolConfig struct { Services struct { Geth struct { HTTPRpc struct { + Internal string `json:"internal"` External string `json:"external"` } `json:"http_rpc"` } `json:"geth"` @@ -69,6 +70,7 @@ type summaryForBFLToolConfig struct { Agglayer struct { Services struct { GrpcRPC struct { + Internal string `json:"internal"` External string `json:"external"` } `json:"grpc_rpc"` AdminAPI struct { @@ -85,6 +87,7 @@ type summaryForBFLToolConfig struct { } `json:"aggkit"` OpGeth struct { HTTPRpc struct { + Internal string `json:"internal"` External string `json:"external"` } `json:"http_rpc"` } `json:"op-geth"` @@ -1200,6 +1203,15 @@ func buildBFLToolConfig(t *testing.T, aggsenderRPCURL, certExitsFile string) *bf l2URL := l2Network.Services.OpGeth.HTTPRpc.External agglayerGRPCURL := summary.Networks.Agglayer.Services.GrpcRPC.External bridgeServiceURL := l2Network.Services.Aggkit.BridgeService.External + l1InternalURL := summary.Networks.L1.Services.Geth.HTTPRpc.Internal + l2InternalURL := l2Network.Services.OpGeth.HTTPRpc.Internal + agglayerInternalGRPCURL := summary.Networks.Agglayer.Services.GrpcRPC.Internal + require.NotEmpty(t, l1InternalURL, "L1 internal RPC URL missing from summary.json") + require.NotEmpty(t, l2InternalURL, "L2 internal RPC URL missing from summary.json") + require.NotEmpty(t, agglayerInternalGRPCURL, "Agglayer internal gRPC URL missing from summary.json") + require.NotEmpty(t, l1URL, "L1 external RPC URL missing from summary.json") + require.NotEmpty(t, l2URL, "L2 external RPC URL missing from summary.json") + require.NotEmpty(t, agglayerGRPCURL, "Agglayer external gRPC URL missing from summary.json") sovereignAdminKeyPath := filepath.Join(testEnv.EnvDir, "config", "001", "sovereignadmin.keystore") @@ -1208,11 +1220,13 @@ func buildBFLToolConfig(t *testing.T, aggsenderRPCURL, certExitsFile string) *bf content, err := os.ReadFile(originalCfgPath) require.NoError(t, err) - // Patch internal docker container URLs with external host-accessible URLs. + // Patch the internal Docker URLs from summary.json with their external, + // host-accessible counterparts. Do not key this on service names: snapshot + // environments use different execution clients and Docker DNS aliases. patched := string(content) - patched = strings.ReplaceAll(patched, "http://geth:8545", l1URL) - patched = strings.ReplaceAll(patched, "http://op-geth-001:8545", l2URL) - patched = strings.ReplaceAll(patched, "http://agglayer:4443", agglayerGRPCURL) + patched = strings.ReplaceAll(patched, l1InternalURL, l1URL) + patched = strings.ReplaceAll(patched, l2InternalURL, l2URL) + patched = strings.ReplaceAll(patched, agglayerInternalGRPCURL, agglayerGRPCURL) // Optional override file line. certExitsFileLine := "" diff --git a/test/e2e/bridge_l2_l2_test.go b/test/e2e/bridge_l2_l2_test.go index 4cc84b2d0..994e24583 100644 --- a/test/e2e/bridge_l2_l2_test.go +++ b/test/e2e/bridge_l2_l2_test.go @@ -16,7 +16,7 @@ import ( // MintableERC20 is an L2A-native token, so it bypasses the Local Balance Tree underflow check // that would block bridging native ETH before any L1->L2 bridge has been performed. // -// It requires the multi-chain env (EnvOpPP2Chains): when run against a single-chain env +// It requires a multi-chain env: when run against a single-chain env // (testEnv.L2B == nil) the test is skipped. Bridge-service or on-chain failures fail loudly. func TestBridgeL2ToL2(t *testing.T) { if testing.Short() { @@ -24,7 +24,7 @@ func TestBridgeL2ToL2(t *testing.T) { } require.NotNil(t, testEnv, "testEnv must be set by TestMain") if testEnv.L2B == nil { - t.Skip("L2->L2 bridge test requires EnvOpPP2Chains (L2B must be non-nil)") + t.Skip("L2->L2 bridge test requires a multi-chain env (L2B must be non-nil)") } // GER propagation L2A -> L1 -> L2B is the slow path; allow a generous budget. diff --git a/test/e2e/bridge_utils.go b/test/e2e/bridge_utils.go index 845168fef..45e266c96 100644 --- a/test/e2e/bridge_utils.go +++ b/test/e2e/bridge_utils.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math/big" + "strings" "time" "github.com/agglayer/aggkit/bridgeservice/client" @@ -25,6 +26,14 @@ import ( // purpose in the other *NoClaim helpers in this file. const bridgeMineWait = 30 * time.Second +// l1BridgeGasLimit avoids an Anvil estimate/mine race in TestMain's parallel health check. The +// concurrent L2->L1 flow can update the L1 info tree after BridgeAsset estimates gas but before its +// transaction is mined, making the original estimate too low for the now-larger tree update. +const l1BridgeGasLimit uint64 = 500_000 + +// alreadyClaimedErrorSelector is the selector for the bridge contract's AlreadyClaimed() error. +const alreadyClaimedErrorSelector = "0x646cf558" + // l1MineDiagnosticsWait bounds how long waitMinedL1WithDiagnostics waits for an L1 tx to be mined. // Normal L1 mining takes ~4s; 4 minutes is far above that while still failing fast enough to leave // budget for teardown log capture if the tx never lands. @@ -148,7 +157,12 @@ func BridgeL1ToL2(ctx context.Context, env *envs.Env, l1Opts, l2Opts *bind.Trans return fmt.Errorf("failed to get initial L2 balance: %w", err) } l1Opts.Value = bridgeAmount - defer func() { l1Opts.Value = nil }() + originalGasLimit := l1Opts.GasLimit + l1Opts.GasLimit = l1BridgeGasLimit + defer func() { + l1Opts.Value = nil + l1Opts.GasLimit = originalGasLimit + }() tx, err := env.L1.Contracts.Bridge.BridgeAsset( l1Opts, l2NetworkID, destinationAddress, bridgeAmount, common.Address{}, forceUpdateGlobalExitRoot, nil, @@ -257,24 +271,62 @@ func BridgeL1ToL2(ctx context.Context, env *envs.Env, l1Opts, l2Opts *bind.Trans } time.Sleep(time.Second) } - log.Debugf("sending claim transaction on L2") - claimTx, err := env.L2.Contracts.L2Bridge.ClaimAsset( - l2Opts, smtProofLocalExitRoot, smtProofRollupExitRoot, - bridge.GlobalIndex, mainnetExitRoot, rollupExitRoot, - bridge.OriginNetwork, originTokenAddress, bridge.DestinationNetwork, - destinationAddress, bridgeAmount, metadata, - ) - if err != nil { - return fmt.Errorf("failed to send claim transaction: %w", err) - } - log.Debugf("L2 claim tx submitted, waiting for mining: tx=%s", claimTx.Hash().Hex()) - claimReceipt, err := bind.WaitMined(ctx, env.Clients.L2, claimTx) + // Some envs (e.g. anvil-2chains) run AutoClaim's L1ToL2BridgeDetector on this destination + // network, which may claim the deposit before this helper gets to it -- a real race, not a + // bug, since AutoClaim polls independently of this test. Check IsClaimed first (same check + // autoclaim_test.go already uses) so a legitimately-already-claimed deposit is treated as + // success instead of failing on the bridge contract's AlreadyClaimed revert. + alreadyClaimed, err := env.L2.Contracts.L2Bridge.IsClaimed(callOpts, depositCount, bridge.OriginNetwork) if err != nil { - return fmt.Errorf("failed to wait for claim tx: %w", err) + return fmt.Errorf("failed to check IsClaimed: %w", err) } - log.Debugf("L2 claim tx mined: tx=%s block=%d", claimTx.Hash().Hex(), claimReceipt.BlockNumber.Uint64()) - if claimReceipt.Status != ethtypes.ReceiptStatusSuccessful { - return errors.New("claim transaction failed") + if alreadyClaimed { + log.Debugf("deposit already claimed (likely by AutoClaim): deposit_count=%d", depositCount) + } else { + log.Debugf("sending claim transaction on L2") + claimTx, err := env.L2.Contracts.L2Bridge.ClaimAsset( + l2Opts, smtProofLocalExitRoot, smtProofRollupExitRoot, + bridge.GlobalIndex, mainnetExitRoot, rollupExitRoot, + bridge.OriginNetwork, originTokenAddress, bridge.DestinationNetwork, + destinationAddress, bridgeAmount, metadata, + ) + if err != nil { + // AutoClaim may have won the race between our IsClaimed check above and this send + // (its own poll loop runs concurrently and independently). Anvil's gas estimation sees + // pending transactions, while the default IsClaimed call reads latest state, so wait for + // that pending AutoClaim transaction to be mined before deciding whether this is fatal. + if !strings.Contains(err.Error(), alreadyClaimedErrorSelector) { + return fmt.Errorf("failed to send claim transaction: %w", err) + } + claimedByAutoClaim := false + for i := 0; i < 30; i++ { + reClaimed, reErr := env.L2.Contracts.L2Bridge.IsClaimed( + callOpts, depositCount, bridge.OriginNetwork) + if reErr == nil && reClaimed { + claimedByAutoClaim = true + break + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + } + } + if !claimedByAutoClaim { + return fmt.Errorf("failed to confirm competing AutoClaim transaction: %w", err) + } + log.Debugf("claim transaction lost the race to AutoClaim: deposit_count=%d", depositCount) + } else { + log.Debugf("L2 claim tx submitted, waiting for mining: tx=%s", claimTx.Hash().Hex()) + claimReceipt, err := bind.WaitMined(ctx, env.Clients.L2, claimTx) + if err != nil { + return fmt.Errorf("failed to wait for claim tx: %w", err) + } + log.Debugf("L2 claim tx mined: tx=%s block=%d", claimTx.Hash().Hex(), claimReceipt.BlockNumber.Uint64()) + if claimReceipt.Status != ethtypes.ReceiptStatusSuccessful { + return errors.New("claim transaction failed") + } + } } finalL2Balance, err := env.Clients.L2.BalanceAt(ctx, destinationAddress, nil) if err != nil { @@ -882,7 +934,7 @@ func BridgeL2ToL2NoClaim( log.Infof("[%s] L2->L2 bridge found in origin bridge service, deposit_count=%d", label, bridge.DepositCount) // Wait for the origin bridge to be included in the L1 Info Tree (its exit root has settled to L1). - // In the batcher-less op-pp env this only happens once the origin aggsender certifies past the + // In a local snapshot env this only happens once the origin aggsender certifies past the // bridge block, which the test drives via background L1->L2 claim priming on L2A. log.Debugf("[%s] waiting for L2->L2 bridge inclusion in L1 Info Tree: deposit_count=%d", label, depositCount) var l1InfoTreeIndex uint32 diff --git a/test/e2e/envs/anvil-2chains/README.md b/test/e2e/envs/anvil-2chains/README.md new file mode 100644 index 000000000..9c7fb37af --- /dev/null +++ b/test/e2e/envs/anvil-2chains/README.md @@ -0,0 +1,129 @@ +# anvil-2chains + +`AGGKIT_E2E_ENV=anvil-2chains` + +Two independent anvil-backed L2 sovereign chains (L2-001 chain 20201, L2-002 chain 20202) +settling PessimisticProof certificates against a single anvil L1 (chain 271828) through one +agglayer, each with its own aggkit instance (merged aggsender + aggoracle + bridge + autoclaim +-- no separate `-bridge` sidecar), fronted by a shared aggkit-proxy. Sourced from a +kurtosis-cdk anvil devnet snapshot rather than a live `kurtosis run` per test invocation, +matching the `op-pp`/`op-pp-2chains` pattern in this directory. + +## Provenance + +- **kurtosis-cdk commit:** `fc160450b55e64332436f11c091c61130c64030f` + (`0xPolygon/kurtosis-cdk`, branch `feat/aggkit-bridge-ui-backend`, PR #929's head). +- **Params files** (two sequential `kurtosis run` invocations into the same enclave): + - [`params-aggkit-anvil-l2l2-run1.yml`](https://github.com/0xPolygon/kurtosis-cdk/blob/fc160450b55e64332436f11c091c61130c64030f/params-aggkit-anvil-l2l2-run1.yml) + -- deploys L1 (anvil-001) + agglayer + rollup 1 (network_id 1, `aggkit-001`). + - [`params-aggkit-anvil-l2l2-run2.yml`](https://github.com/0xPolygon/kurtosis-cdk/blob/fc160450b55e64332436f11c091c61130c64030f/params-aggkit-anvil-l2l2-run2.yml) + -- adds rollup 2 (network_id 2, `aggkit-002`) into the same enclave, plus the + `aggkit-proxy-001` / dev-ui stack that this e2e env doesn't use. +- **Snapshot build:** `snapshot/snapshot.sh --flavor anvil-aggkit --tag ` (see + `.github/workflows/snapshot-devui.yml`), which seeds fixtures, captures live state, builds + self-contained images, and emits `docker-compose.yml` / `docker-compose.mounts.yml` / + `summary.json` / `config/` under `snapshots/-/`. +- **Publish run:** [GitHub Actions run 31787941750](https://github.com/0xPolygon/kurtosis-cdk/actions/runs/31787941750) + (`workflow_dispatch`, `publish=true`, ref `feat/aggkit-bridge-ui-backend`, resolved base tag + `fc160450b55e`). Independent full `test` run at the same HEAD (all 18 jobs green, zero + skipped): [run 31787908220](https://github.com/0xPolygon/kurtosis-cdk/actions/runs/31787908220). +- **Config tree source:** this directory's `config/` was copied from the same commit's local + re-run of the snapshot pipeline (`snapshots/k8-20260814-095314/config/`, + `docker-compose.mounts.yml` variant -- bare upstream `agglayer`/`aggkit` images with + bind-mounted config, not the fully-baked default variant), then renamed per the mapping + documented in `snapshot/scripts/extract-state.sh` (kurtosis-cdk names each directory after + its own service, e.g. `config/aggkit-001/config.toml`; this env instead keys per-L2 + directories by bare network prefix and calls the aggkit config file + `aggkit-config.toml`, matching `op-pp-2chains`'s own layout): + + | kurtosis-cdk (emitted) | this env | + |---|---| + | `config/agglayer/config.toml` | `config/agglayer/config.toml` | + | `config/agglayer/aggregator.keystore` | `config/agglayer/aggregator.keystore` | + | `config/aggkit-001/config.toml` | `config/001/aggkit-config.toml` | + | `config/aggkit-001/{sequencer,aggoracle,sovereignadmin}.keystore` | `config/001/{sequencer,aggoracle,sovereignadmin}.keystore` | + | `config/aggkit-002/config.toml` | `config/002/aggkit-config.toml` | + | `config/aggkit-002/{sequencer,aggoracle,sovereignadmin}.keystore` | `config/002/{sequencer,aggoracle,sovereignadmin}.keystore` | + | `config/aggkit-proxy-001/config.toml` | `config/aggkit-proxy/aggkit-proxy.toml` | + + Only hostnames/internal service names needed to line up (they already did -- this env keeps + the same compose service names the bundle uses: `anvil-001`, `l2-anvil-001`, `l2-anvil-002`, + `agglayer`, `aggkit-001`, `aggkit-002`, `aggkit-proxy-001`) and only host ports changed (see + below); no contract addresses or private keys needed to change, because the bundle's L1/L2 + mnemonics are byte-identical to `op-pp-2chains`'s own (`giant issue aisle ... athlete` / `test + test ... junk`), so every deterministically-derived contract address matches address-for-address. + +## Images (`name@digest`) + +Independently re-verified as anonymously pullable from `ghcr.io/0xpolygon` (see the plan's +`K8-evidence/HANDOFF.md` and `06-anonymous-pull-proof.log` / `08-registry-digest-comparison.txt` +for the verification detail): + +| Service | Image | Tag | +|---|---|---| +| `anvil-001` | `ghcr.io/0xpolygon/kurtosis-cdk-snapshot-anvil-001@sha256:006932fc49ce501c8d6f8c3f4ac3b5873ec14b59b101b4f4e9db02b169e6c0c9` | `v1.5.1-1786700247` | +| `l2-anvil-001` | `ghcr.io/0xpolygon/kurtosis-cdk-snapshot-l2-anvil-001@sha256:e9bbeb7f9a76a4ea725f194059c6b23d49c65e89a8a00241d6df3b687c8ccbb8` | `v1.5.1-1786700247` | +| `l2-anvil-002` | `ghcr.io/0xpolygon/kurtosis-cdk-snapshot-l2-anvil-002@sha256:3555d50518f6f72d5811edd759293ba205ac192c04192695afc046c2cb595ef0` | `v1.5.1-1786700247` | +| `agglayer` | `ghcr.io/0xpolygon/kurtosis-cdk-snapshot-agglayer@sha256:5a47d3778657ba618ff7dfc99dfd55a3863097d5fff4f960a0740d4d0ae80073` | `0.6.0-rc.8-1786700247` | +| `aggkit-001` / `aggkit-002` / `aggkit-proxy-001` | **not** the snapshot's baked aggkit image (`kurtosis-cdk-snapshot-aggkit-*`) -- this env runs `aggkit:local`, this repo's own build, so the binary under test is always the checkout in this worktree, not a pinned upstream aggkit release. | + +`aggkit:local` is built by `make build-docker` (`docker build -t aggkit:local ... -f ./Dockerfile +.`, see the top-level Makefile) and is expected to already exist before running this env's +tests, exactly like `op-pp`/`op-pp-2chains`. + +## Chain / network IDs + +| | chain_id | network_id | +|---|---|---| +| L1 (`anvil-001`) | 271828 | 0 | +| L2-001 (`l2-anvil-001`) | 20201 | 1 | +| L2-002 (`l2-anvil-002`) | 20202 | 2 | + +## Ports (host, collision-checked against `op-pp` and `op-pp-2chains`) + +| Service | Container port(s) | Host port(s) | +|---|---|---| +| `anvil-001` | 8545 | 13545 | +| `agglayer` | 4443/4444/4446/9092 | 13443/13444/13446/13092 | +| `l2-anvil-001` | 8545 | 14545 | +| `aggkit-001` | 5576/5577/5579 | 14576/14577/14579 | +| `l2-anvil-002` | 8545 | 15545 | +| `aggkit-002` | 5576/5577/5579 | 15576/15577/15579 | +| `aggkit-proxy-001` | 8080 | 15601 | + +## Known deviations from the design note + +`config/agglayer/config.toml`'s `[full-node-rpcs]` / `[proof-signers]` here has entries for +**both** network 1 and network 2 (unlike `op-pp-2chains`'s own config, which only has a +network-1 entry). This is intentionally carried forward as emitted by the kurtosis-cdk bundle +rather than trimmed to match `op-pp-2chains`'s pattern: the bundle's own two-network config is +the one that was actually exercised end-to-end by kurtosis-cdk's test run (18 jobs green, +including PessimisticProof settlement on both rollups), so it is a stronger working precedent +here than `op-pp-2chains`'s incomplete (network-1-only) config for a topology that doesn't +happen to need network 2's entry for its own tests to pass. + +## Regenerating this env + +1. Check out kurtosis-cdk at `fc160450b55e64332436f11c091c61130c64030f` (or a descendant that + hasn't changed the anvil-aggkit flavor's shape). +2. `kurtosis run --enclave=cdk --args-file=params-aggkit-anvil-l2l2-run1.yml .` +3. `kurtosis run --enclave=cdk --args-file=params-aggkit-anvil-l2l2-run2.yml .` +4. `snapshot/snapshot.sh cdk --flavor anvil-aggkit --tag ` -- produces + `snapshots/cdk-/{docker-compose.yml,docker-compose.mounts.yml,summary.json,config/}`. +5. Copy `docker-compose.mounts.yml` as the starting shape for this directory's + `docker-compose.yml` (swap in digest-pinned image refs for `anvil-001`/`l2-anvil-001`/ + `l2-anvil-002`/`agglayer`, and `aggkit:local` for `aggkit-001`/`aggkit-002`/ + `aggkit-proxy-001`; keep the TCP-connect healthcheck on `agglayer` -- see the file header + comment in `docker-compose.yml` for why). +6. Copy `config/` into this directory's `config/`, renaming per the mapping table above. +7. Rewrite `summary.json` into aggkit's own schema (see `test/e2e/envs/loader.go`'s + `summaryJSON`/`summaryL2Network` structs for the exhaustive list of keys actually read) using + this bundle's own `summary.json` (`chain_ids`, `network_ids`, `accounts.funded` filtered by + `funded_on`, `networks.l1.contracts`, `networks.l2.*.contracts`) plus the host ports chosen + in step 5. +8. Confirm `TriggerCertMode = "ASAP"` is still explicit in both `config/001/aggkit-config.toml` + and `config/002/aggkit-config.toml` (`Auto` silently resolves to `EpochBased` for a + PessimisticProof aggsender). +9. To publish new digests for step 5's image refs, re-run + `.github/workflows/snapshot-devui.yml` with `publish=true` and copy the resulting + `name@digest` triples from the run's summary/logs. diff --git a/test/e2e/envs/anvil-2chains/config/001/aggkit-config.toml b/test/e2e/envs/anvil-2chains/config/001/aggkit-config.toml new file mode 100644 index 000000000..8cb2e6bb6 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/001/aggkit-config.toml @@ -0,0 +1,769 @@ +# ============================================================================== +# _ ____ ____ _ _____ _____ +# / \ / ___|/ ___| |/ /_ _|_ _| +# / _ \| | _| | _| ' / | | | | +# / ___ \ |_| | |_| | . \ | | | | +# /_/ \_\____|\____|_|\_\___| |_| +# +# This is a reference config file used by the Kurtosis CDK Testing +# setup. The values here should work, but are necessarily meant for +# production environments. DYOR +# The below configs are the default mandatory parameters to be used. +# ============================================================================== + +PathRWData = "/tmp" +L1URL = "http://anvil-001:8545" +L2URL = "http://l2-anvil-001:8545" +OpNodeURL = "http://l2-anvil-001:8545" + +# Check if agglayer grpc or readrpc should be used for AggLayerURL +AggLayerURL = "http://agglayer:4443" + +AggchainProofURL= "aggkit-prover-001:4446" +SequencerPrivateKeyPath = "/etc/aggkit/sequencer.keystore" +SequencerPrivateKeyPassword = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" +RPCURL = "http://l2-anvil-001:8545" + +# These values can be overridden directly from genesis.json +rollupCreationBlockNumber = "60" +rollupManagerCreationBlockNumber = "60" +genesisBlockNumber = "60" +# ------------------------------------------------------------------------------ + +# ============================================================================== +# _ _ ____ ___ _ _ _____ ___ ____ +# | | / |/ ___/ _ \| \ | | ___|_ _/ ___| +# | | | | | | | | | \| | |_ | | | _ +# | |___| | |__| |_| | |\ | _| | | |_| | +# |_____|_|\____\___/|_| \_|_| |___\____| +# +# ------------------------------------------------------------------------------ +[L1Config] +# ------------------------------------------------------------------------------ +# URL is the L1 network url +# ------------------------------------------------------------------------------ +URL = "http://anvil-001:8545" + +# ------------------------------------------------------------------------------ +# L1 chain id +# ------------------------------------------------------------------------------ +chainId = "271828" + +# ------------------------------------------------------------------------------ +# Address of the zkevm global exit root contract on L1 +# ------------------------------------------------------------------------------ +polygonZkEVMGlobalExitRootAddress = "0x1f7ad7caA53e35b4f0D138dC5CBF91aC108a2674" + +# ------------------------------------------------------------------------------ +# Address of the rollup manager contract on L1 +# ------------------------------------------------------------------------------ +polygonRollupManagerAddress = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" + +# ------------------------------------------------------------------------------ +# Address of the pol token address on L1 +# ------------------------------------------------------------------------------ +polTokenAddress = "0xEdE9cf798E0fE25D35469493f43E88FeA4a5da0E" + +# ------------------------------------------------------------------------------ +# Address of the sovereign rollup contract on L2 +# ------------------------------------------------------------------------------ +polygonZkEVMAddress = "0x414e9E227e4b589aF92200508aF5399576530E4e" + +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +# ============================================================================== +# _ ____ ____ ___ _ _ _____ ___ ____ +# | | |___ \ / ___/ _ \| \ | | ___|_ _/ ___| +# | | __) | | | | | | \| | |_ | | | _ +# | |___ / __/| |__| |_| | |\ | _| | | |_| | +# |_____|_____|\____\___/|_| \_|_| |___\____| +# +# ------------------------------------------------------------------------------ +[L2Config] +# ------------------------------------------------------------------------------ +# Address of the sovereign global exit root proxy contract on L2 +# ------------------------------------------------------------------------------ +GlobalExitRootAddr = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +# ============================================================================== +# _ ___ ____ +# | | / _ \ / ___| +# | | | | | | | _ +# | |__| |_| | |_| | +# |_____\___/ \____| +# +# ------------------------------------------------------------------------------ +[Log] +# ------------------------------------------------------------------------------ +# Environment generally dictates the format of the logs and the +# sampling rate. We often default to production even for development +# because of the JSON encoding. +# +# https://github.com/uber-go/zap/blob/a55bdc32f526699c3b4cc51a2cc97e944d02fbbf/config.go#L120 +# https://github.com/uber-go/zap/blob/a55bdc32f526699c3b4cc51a2cc97e944d02fbbf/config.go#L161 +# ------------------------------------------------------------------------------ +Environment = "development" + +# ------------------------------------------------------------------------------ +# Level determines the log level that will be written to the +# log. Generally we'll switch to debug if we want to troubleshoot +# something specifically otherwise we leave it at info +# ------------------------------------------------------------------------------ +Level = "info" + +# ------------------------------------------------------------------------------ +# Outputs define the output paths for writing logs. The default is to +# write to stderr, but other output paths should be supported +# +# https://github.com/uber-go/zap/blob/a55bdc32f526699c3b4cc51a2cc97e944d02fbbf/writer.go#L32-L50 +# ------------------------------------------------------------------------------ +Outputs = ["stderr"] + +# ============================================================================== +# ____ ____ ____ +# | _ \| _ \ / ___| +# | |_) | |_) | | +# | _ <| __/| |___ +# |_| \_\_| \____| +# +# ------------------------------------------------------------------------------ +[RPC] +# ------------------------------------------------------------------------------ +# Port will configure the port that the JSON RPC server will listen on +# ------------------------------------------------------------------------------ +Port = "5576" + +# ============================================================================== +# ____ _____ ____ _____ +# | _ \| ____/ ___|_ _| +# | |_) | _| \___ \ | | +# | _ <| |___ ___) || | +# |_| \_\_____|____/ |_| +# +# ------------------------------------------------------------------------------ +[PublicREST] +# ------------------------------------------------------------------------------ +# Port will configure the port that the REST API HTTP server will +# listen on +# ------------------------------------------------------------------------------ +Port = "5577" + +[AdminREST] +Port = 5579 + +# ============================================================================== +# _ ____ ____ ____ _____ _ _ ____ _____ ____ +# / \ / ___|/ ___/ ___|| ____| \ | | _ \| ____| _ \ +# / _ \| | _| | _\___ \| _| | \| | | | | _| | |_) | +# / ___ \ |_| | |_| |___) | |___| |\ | |_| | |___| _ < +# /_/ \_\____|\____|____/|_____|_| \_|____/|_____|_| \_\ +# +# ------------------------------------------------------------------------------ +[AggSender] +# ------------------------------------------------------------------------------ +# StoragePath is the path of the sqlite db for the AggSender to store the data +# ------------------------------------------------------------------------------ +# StoragePath = "/tmp" + +# ------------------------------------------------------------------------------ +# AggsenderPrivateKey is the private key which is used to sign certificates +# ------------------------------------------------------------------------------ +AggSenderPrivateKey = {Path = "/etc/aggkit/sequencer.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m"} + +# ------------------------------------------------------------------------------ +# Defines if a check on aggsender proposer startup will be performed +# to see if the proposer is in the multisig committee +# ------------------------------------------------------------------------------ +# RequireCommitteeMembershipCheck = false +Mode = "PessimisticProof" +CheckStatusCertificateInterval = "1s" + +# ------------------------------------------------------------------------------ +# TriggerCertMode is the mode used to trigger certificate sending +# Valid values are: "EpochBased", "NewBridge", "ASAP", "Auto" +# EpochBased: this is the legacy mode that waits until reach a percentage of a epoch (you can configure here: AggSender.TriggerEpochBased) +# ASAP: this mode try to generate a new certificate after a successful settled certificate +# NewBridge: Each time that a new bridge is done in L2 it generate a certificate (if it's possible) (experimental) +# ------------------------------------------------------------------------------ +TriggerCertMode = "ASAP" + +# # Encouraged to use the default values - the parameters are left commented out for reference +# +# [AggSender.TriggerASAP] +# # Delay between the moment the aggsender becomes idle and when it sends a new certificate trigger +# DelayBetweenCertificates = "1s" +# # Minimum time that must elapse between certificate generation triggers, regardless of the trigger source +# MinimumNewCertificateInterval = "5s" +# # When enabled, the ASAP trigger will automatically generate certificate triggers when new bridge events are detected on L2 +# OnNewL2Bridge = false +# + +# ------------------------------------------------------------------------------ +# MaxCertSize is the maximum size of the certificate +# i.e (the emitted certificate cannot be bigger that this size) +# 0 is infinite +# ------------------------------------------------------------------------------ +# MaxCertSize = 0 +[AggSender.ValidatorClient] +URL = "aggkit-validator-001:5578" + +[AggSender.AggkitProverClient] +UseTLS = false + +# ------------------------------------------------------------------------------ +# URLRPCL2 is the URL of the L2 RPC node +# ------------------------------------------------------------------------------ +# URLRPCL2 = "http://l2-anvil-001:8545" + +# ------------------------------------------------------------------------------ +# EpochNotificationPercentage indicates the percentage of the epoch +# the AggSender should send the certificate +# 0 -> Begin +# 50 -> Middle +# ------------------------------------------------------------------------------ +# EpochNotificationPercentage = 50 + +# ------------------------------------------------------------------------------ +# MaxRetriesStoreCertificate is the maximum number of retries to store a certificate +# 0 is infinite +# ------------------------------------------------------------------------------ +# MaxRetriesStoreCertificate = 3 + +# ------------------------------------------------------------------------------ +# DelayBetweenRetries is the delay between retries +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# ------------------------------------------------------------------------------ +# DelayBetweenRetries = 5s + +# ------------------------------------------------------------------------------ +# BridgeMetadataAsHash is a flag to import the bridge metadata as hash +# ------------------------------------------------------------------------------ +# BridgeMetadataAsHash = false + +# ------------------------------------------------------------------------------ +# DryRun is a flag to enable the dry run mode +# in this mode the AggSender will not send the certificates to Agglayer +# ------------------------------------------------------------------------------ +# DryRun = false + +# ------------------------------------------------------------------------------ +# EnableRPC is a flag to enable the RPC for aggsender +# ------------------------------------------------------------------------------ +# EnableRPC = false + +[AggSender.AgglayerClient] + +[[AggSender.AgglayerClient.APIRateLimits]] +MethodName = "SendCertificate" + +[AggSender.AgglayerClient.APIRateLimits.RateLimit] +# Disable limit +NumRequests = 0 + +[AggSender.AgglayerClient.GRPC] +URL = "http://agglayer:4443" +MinConnectTimeout = "5s" +RequestTimeout = "300s" +UseTLS = false + +[AggSender.AgglayerClient.GRPC.Retry] +InitialBackoff = "1s" +MaxBackoff = "10s" +BackoffMultiplier = 2.0 +MaxAttempts = 20 + +[AggSender.StorageRetainCertificatesPolicy] +# ------------------------------------------------------------------------------ +# RetainCertificatesCount controls how many certificates to retain on storage. +# If set to zero, all certificates will be stored. +# ------------------------------------------------------------------------------ +# RetainCertificatesCount = 0 + +# ------------------------------------------------------------------------------ +# KeepCertificatesHistory is a flag to keep the certificates history on storage +# ------------------------------------------------------------------------------ +# KeepCertificatesHistory = true + +# ------------------------------------------------------------------------------ +# RetryCertAfterInError when a cert pass to 'InError'state +# the AggSender will try to resend it immediately +# ------------------------------------------------------------------------------ +# RetryCertAfterInError = false + +# ------------------------------------------------------------------------------ +# RequireNoFEPBlockGap is true if the AggSender should not accept a gap between +# lastBlock from lastCertificate and first block of FEP +# ------------------------------------------------------------------------------ +# RequireNoFEPBlockGap = false + +# ------------------------------------------------------------------------------ +# RequireOneBridgeInPPCertificate is a flag to force the AggSender to have at least one bridge exit +# for the Pessimistic Proof certificates +# ------------------------------------------------------------------------------ +# RequireOneBridgeInPPCertificate = false + +# ------------------------------------------------------------------------------ +# MaxL2BlockNumber is the last L2 block number that is going to be included in a certificate +# 0 means disabled +# ------------------------------------------------------------------------------ +# MaxL2BlockNumber = 0 + +# ------------------------------------------------------------------------------ +# StopOnFinishedSendingAllCertificates is a flag to stop the AggSender when it finishes sending all certificates +# up to MaxL2BlockNumber +# ------------------------------------------------------------------------------ +# StopOnFinishedSendingAllCertificates = false + +# ============================================================================== +# _ ____ ____ ___ ____ _ ____ _ _____ +# / \ / ___|/ ___|/ _ \| _ \ / \ / ___| | | ____| +# / _ \| | _| | _| | | | |_) | / _ \| | | | | _| +# / ___ \ |_| | |_| | |_| | _ < / ___ \ |___| |___| |___ +# /_/ \_\____|\____|\___/|_| \_\/_/ \_\____|_____|_____| +# +# ------------------------------------------------------------------------------ +[AggOracle] +# ------------------------------------------------------------------------------ +# TargetChainType currently only supports "EVM" +# ------------------------------------------------------------------------------ +# TargetChainType = "EVM" + +# ------------------------------------------------------------------------------ +# URLRPCL1 is the URL of the L1 RPC node +# ------------------------------------------------------------------------------ +# URLRPCL1 = "http://anvil-001:8545" + +# ------------------------------------------------------------------------------ +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# A3 measurement (plans/snapshot-v2-aggkit-e2e/A3-evidence/): this poll +# period is the single largest lever found for this env's measured +# TestMain post-test bridge check. At the prior 10s value (which matches +# this repo's own config/default.go template default -- not a regression, +# just conservative), the L1->L2 GER-injection wait showed a bimodal +# ~36s/~43s split across otherwise-identical runs depending on poll-cycle +# alignment; at 1s it collapsed to a tight ~35-37s cluster (5 consecutive +# runs, spread <2s). Lowering only changes how often AggOracle checks L1 +# for a new GER to inject -- it does not touch any finality/reorg-safety +# knob, so this is not a raciness trade-off. +# ------------------------------------------------------------------------------ +WaitPeriodNextGER = "1s" + +# ------------------------------------------------------------------------------ +# +# ------------------------------------------------------------------------------ +EnableAggOracleCommittee = false + +[AggOracle.EVMSender] +# ------------------------------------------------------------------------------ +# Address of the sovereign global exit root proxy contract on L2 +# ------------------------------------------------------------------------------ +GlobalExitRootL2 = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" + +# ------------------------------------------------------------------------------ +# URLRPCL2 is the URL of the L2 RPC node +# ------------------------------------------------------------------------------ +# URLRPCL2 = "http://l2-anvil-001:8545" + +# ------------------------------------------------------------------------------ +# GasOffset is the gas to add on the estimated gas when sending the claim txs +# ------------------------------------------------------------------------------ +# GasOffset = 0 + +# ------------------------------------------------------------------------------ +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# A3 measurement: restores this repo's own config/default.go template +# default (1s) -- the prior 10s value here was 10x slower than upstream's +# own default, not an intentional devnet choice. Measured with +# WaitPeriodNextGER alone at 1s (WaitPeriodMonitorTx still 10s) this did +# NOT collapse the bimodal split on its own (median stayed ~43s); it is +# kept at the default anyway since it costs nothing and is the correct +# baseline value. +# ------------------------------------------------------------------------------ +WaitPeriodMonitorTx = "1s" + +# ------------------------------------------------------------------------------ +# +# ------------------------------------------------------------------------------ + +[AggOracle.EVMSender.EthTxManager] +# ------------------------------------------------------------------------------ +# PrivateKeys defines all the key store files that are going +# to be read in order to provide the private keys to sign the L1 txs +# ------------------------------------------------------------------------------ +PrivateKeys = [{Path = "/etc/aggkit/aggoracle.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m"}] + +# ------------------------------------------------------------------------------ +# FrequencyToMonitorTxs frequency of the resending failed txs +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# ------------------------------------------------------------------------------ +# FrequencyToMonitorTxs = "1s" + +# ------------------------------------------------------------------------------ +# WaitTxToBeMined time to wait after transaction was sent to the ethereum +# ------------------------------------------------------------------------------ +# WaitTxToBeMined = "2s" + +# ------------------------------------------------------------------------------ +# GetReceiptMaxTime is the max time to wait to get the receipt of the mined transaction +# ------------------------------------------------------------------------------ +# GetReceiptMaxTime = "250ms" + +# ------------------------------------------------------------------------------ +# GetReceiptWaitInterval is the time to sleep before trying to get the receipt of the mined transaction +# ------------------------------------------------------------------------------ +# GetReceiptWaitInterval = "1s" + +# ------------------------------------------------------------------------------ +# ForcedGas is the amount of gas to be forced in case of gas estimation error +# ------------------------------------------------------------------------------ +# ForcedGas = 0 + +# ------------------------------------------------------------------------------ +# GasPriceMarginFactor is used to multiply the suggested gas price provided by the network +# in order to allow a different gas price to be set for all the transactions and making it +# easier to have the txs prioritized in the pool, default value is 1. +# +# example: +# suggested gas price: 100 +# GasPriceMarginFactor: 1 +# gas price = 100 +# +# suggested gas price: 100 +# GasPriceMarginFactor: 1.1 +# gas price = 110 +# ------------------------------------------------------------------------------ +# GasPriceMarginFactor = 1 + +# ------------------------------------------------------------------------------ +# MaxGasPriceLimit helps avoiding transactions to be sent over an specified +# gas price amount, default value is 0, which means no limit. +# If the gas price provided by the network and adjusted by the GasPriceMarginFactor +# is greater than this configuration, transaction will have its gas price set to +# the value configured in this config as the limit. +# +# example: +# suggested gas price: 100 +# gas price margin factor: 20% +# max gas price limit: 150 +# tx gas price = 120 +# +# suggested gas price: 100 +# gas price margin factor: 20% +# max gas price limit: 110 +# tx gas price = 110 +# ------------------------------------------------------------------------------ +# MaxGasPriceLimit = 0 + +# ------------------------------------------------------------------------------ +# StoragePath is the path of the internal storage +# ------------------------------------------------------------------------------ +# StoragePath = "/tmp/ethtxmanager-aggoracle.sqlite" + +# ------------------------------------------------------------------------------ +# ReadPendingL1Txs is a flag to enable the reading of pending L1 txs +# It can only be enabled if DBPath is empty +# ------------------------------------------------------------------------------ +# ReadPendingL1Txs = false + +# ------------------------------------------------------------------------------ +# SafeStatusL1NumberOfBlocks overwrites the number of blocks to consider a tx as safe +# overwriting the default value provided by the network +# 0 means that the default value will be used +# ------------------------------------------------------------------------------ +# SafeStatusL1NumberOfBlocks = 5 + +# ------------------------------------------------------------------------------ +# FinalizedStatusL1NumberOfBlocks overwrites the number of blocks to +# consider a tx as finalized overwriting the default value provided by the network +# 0 means that the default value will be used +# ------------------------------------------------------------------------------ +# FinalizedStatusL1NumberOfBlocks = 10 + +[AggOracle.EVMSender.EthTxManager.Etherman] +# ------------------------------------------------------------------------------ +# Needs to be set to be the sovereign L2 chain id +# ------------------------------------------------------------------------------ +L1ChainID = "20201" + +# ============================================================================== +# ____ ____ ___ ____ ____ _____ _ ____ ______ ___ _ ____ +# | __ )| _ \|_ _| _ \ / ___| ____| | |___ \/ ___\ \ / / \ | |/ ___| +# | _ \| |_) || || | | | | _| _| | | __) \___ \\ V /| \| | | +# | |_) | _ < | || |_| | |_| | |___| |___ / __/ ___) || | | |\ | |___ +# |____/|_| \_\___|____/ \____|_____|_____|_____|____/ |_| |_| \_|\____| +# ------------------------------------------------------------------------------ +[BridgeL2Sync] +# ------------------------------------------------------------------------------ +# BridgeAddr is the address of the sovereign bridge contract on L2 +# ------------------------------------------------------------------------------ +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +# ------------------------------------------------------------------------------ +# BlockFinality selects which L2 block type to use when querying the bridge +# contract for synchronization +# Accepted values: +# - LatestBlock +# - SafeBlock +# - PendingBlock +# - FinalizedBlock +# ------------------------------------------------------------------------------ +BlockFinality = "LatestBlock" + +[ReorgDetectorL2] +# ------------------------------------------------------------------------------ +# FinalizedBlock defines which L2 block tag is used as the source of truth when +# checking for reorgs on L2 +# Accepted values: +# - LatestBlock +# - SafeBlock +# - PendingBlock +# - FinalizedBlock +# +# cdk-erigon sovereign chains never advance the "finalized" (nor "safe") block +# tag past genesis, so the reorg detector never drops finalized blocks from its +# tracked set. That set then grows without bound and the detector re-queries a +# header for every tracked block on each check tick; under constrained CI CPU +# this starves the L2 bridge syncer's downloader and the sync silently stalls, +# so L2->L1 bridge claims never become ready. op-stack chains advance +# "finalized" through op-node, and anvil advances it through +# --slots-in-an-epoch; both keep the default tag. Track the latest block for +# cdk-erigon so the tracked set stays bounded (matches BridgeL2Sync). +# ------------------------------------------------------------------------------ +FinalizedBlock = "FinalizedBlock" + +# ------------------------------------------------------------------------------ +# DBPath path of the sqlite db +# ------------------------------------------------------------------------------ +# DBPath = "/tmp/bridgel2sync.sqlite" + +# ------------------------------------------------------------------------------ +# First block that will be queried when starting the synchronization from scratch. +# It should be a number equal or bellow the creation of the bridge contract +# ------------------------------------------------------------------------------ +# InitialBlockNum = 0 + +# ------------------------------------------------------------------------------ +# The amount of blocks that will be queried to the client on each request +# ------------------------------------------------------------------------------ +# SyncBlockChunkSize = 100 + +# ------------------------------------------------------------------------------ +# The time that will be waited when an unexpected error happens before retry +# ------------------------------------------------------------------------------ +# RetryAfterErrorPeriod = "1s" + +# ------------------------------------------------------------------------------ +# The maximum number of consecutive attempts that will happen before panic. +# Any number smaller than zero will be considered as unlimited retries +# ------------------------------------------------------------------------------ +# MaxRetryAttemptsAfterError = -1 + +# ------------------------------------------------------------------------------ +# Time that will be waited when the synchronizer has reached the latest block +# ------------------------------------------------------------------------------ +# WaitForNewBlocksPeriod = "3s" + +# ============================================================================== +# _ _ ___ _ _ _____ ___ _____ ____ _____ _____ ______ ___ _ ____ +# | | / |_ _| \ | | ___/ _ \_ _| _ \| ____| ____/ ___\ \ / / \ | |/ ___| +# | | | || || \| | |_ | | | || | | |_) | _| | _| \___ \\ V /| \| | | +# | |___| || || |\ | _|| |_| || | | _ <| |___| |___ ___) || | | |\ | |___ +# |_____|_|___|_| \_|_| \___/ |_| |_| \_\_____|_____|____/ |_| |_| \_|\____| +# +# ------------------------------------------------------------------------------ +[L1InfoTreeSync] +# ------------------------------------------------------------------------------ +# The initial block number from which to start syncing. +# Default: 0 +# ------------------------------------------------------------------------------ +InitialBlock = "60" + +# ============================================================================== +# _ ____ ____ _____ ____ ______ ___ _ ____ +# | | |___ \ / ___| ____| _ \/ ___\ \ / / \ | |/ ___| +# | | __) | | _| _| | |_) \___ \\ V /| \| | | +# | |___ / __/| |_| | |___| _ < ___) || | | |\ | |___ +# |_____|_____|\____|_____|_| \_\____/ |_| |_| \_|\____| +# ============================================================================== +[L2GERSync] +# ------------------------------------------------------------------------------ +# BlockFinality indicates which finality follows AggLayer accepted values are: +# LatestBlock, SafeBlock, PendingBlock, FinalizedBlock, EarliestBlock +# Default value is "LatestBlock" +# ------------------------------------------------------------------------------ +BlockFinality = "LatestBlock" + +# ============================================================================== +# _ _ __ +# __ _ __ _ __ _ ___| |__ __ _(_)_ __ _ __ _ __ ___ ___ / _| __ _ ___ _ __ +# / _` |/ _` |/ _` |/ __| '_ \ / _` | | '_ \| '_ \| '__/ _ \ / _ \| |_ / _` |/ _ \ '_ \ +#| (_| | (_| | (_| | (__| | | | (_| | | | | | |_) | | | (_) | (_) | _| (_| | __/ | | | +# \__,_|\__, |\__, |\___|_| |_|\__,_|_|_| |_| .__/|_| \___/ \___/|_| \__, |\___|_| |_| +# |___/ |___/ |_| |___/ +# ------------------------------------------------------------------------------ +[AggchainProofGen] +# ------------------------------------------------------------------------------ +# SovereignRollupAddr is the address of the sovereign rollup contract on L1 +# ------------------------------------------------------------------------------ +SovereignRollupAddr = "0x414e9E227e4b589aF92200508aF5399576530E4e" + +# ------------------------------------------------------------------------------ +# GlobalExitRootL2Addr is the address of the GlobalExitRootManager contract on l2 sovereign chain +# this address is needed for the AggchainProof mode of the AggSender +# ------------------------------------------------------------------------------ +GlobalExitRootL2 = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" + +[AggchainProofGen.AggkitProverClient] +# ------------------------------------------------------------------------------ +# UseTLS is a flag to enable the AggkitProver TLS handshake in the AggSender-AggkitProver gRPC connection +# ------------------------------------------------------------------------------ +# UseTLS = false + +# ============================================================================== +# ____ __ _ _ _ +# | _ \ _ __ ___ / _(_) (_)_ __ __ _ +# | |_) | '__/ _ \| |_| | | | '_ \ / _` | +# | __/| | | (_) | _| | | | | | | (_| | +# |_| |_| \___/|_| |_|_|_|_| |_|\__, | +# |___/ +# ------------------------------------------------------------------------------ +[Profiling] +# ------------------------------------------------------------------------------ +# ProfilingHost is the address to bind the profiling server +# Default: "localhost" +# ------------------------------------------------------------------------------ +ProfilingHost = "0.0.0.0" + +# ------------------------------------------------------------------------------ +# ProfilingPort is the port to bind the profiling server +# Default: 6060 +# ------------------------------------------------------------------------------ +ProfilingPort = 6060 + +# ------------------------------------------------------------------------------ +# ProfilingEnabled is the flag to enable/disable the profiling server +# Default: false +# ------------------------------------------------------------------------------ +ProfilingEnabled = true + + +# https://github.com/agglayer/aggkit/pull/744 +[Validator] +EnableRPC = true +# Signer = { Method = "mock" } +Signer = { Method = "local", Path = "/etc/aggkit/sequencer.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" } +# PessimisticProof or AggchainProof +Mode = "PessimisticProof" + +[Validator.ServerConfig] +Host = "0.0.0.0" +Port = 5578 +MaxDecodingMessageSize = 1073741824 # 1GB + +# Is it necessary to specify all of these values again? +[Validator.LerQuerierConfig] +RollupManagerAddr = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" +RollupCreationBlockL1 = "60" + +[Validator.AgglayerClient] +Cached = true + +[Validator.AgglayerClient.ConfigurationCache] +TTL = "15m" +Capacity = 100 + +[Validator.AgglayerClient.GRPC] +URL = "http://agglayer:4443" +UseTLS = false + +# ============================================================================== +# _ _____ ___ ____ _ _ ___ __ __ +# / \ _ _ |_ _/ _ \ / ___| | / \ |_ _| \/ | +# / _ \ | | | | || | | || | | | / _ \ | || |\/| | +# / ___ \|_| | | || |_| || |___| |___ / ___ \ | || | | | +# /_/ \_\__,_| |_| \___/ \____|_____/_/ \_\___|_| |_| +# +# ------------------------------------------------------------------------------ +# Auto Claim is entirely absent from this template's defaults (the aggkit +# binary's own config/default.go supplies working, inert defaults -- +# Claimers = [], L2ToLxBridgeDetector.Enabled = false -- when this section is +# omitted). This block only renders when aggkit_autoclaim_enabled is true, +# i.e. when this instance's own l2_network_id is listed in +# aggkit_autoclaim_destinations (see input_parser.star). It configures a +# SINGLE claimer targeting this instance's own destination network -- never +# network 0 (L1); deposits destined to L1 must stay manual-claim-only for the +# bridge UI. E2E tests enable the required detectors and claimer explicitly; +# keeping them disabled in the baseline prevents unrelated stateful scenarios +# from racing AutoClaim. +# Requires "autoclaim" to also be listed in aggkit_components (see +# _log_autoclaim_warning in aggkit.star). +# ------------------------------------------------------------------------------ +[AutoClaim] +DryRun = false +StoragePath = "/tmp/autoclaim.sqlite" + +[AutoClaim.API] +Enabled = false + +[AutoClaim.L1ToL2BridgeDetector] +Enabled = false +StartBlock = 0 +PollInterval = "3s" +EtrogL1UpgradeBlock = 0 + +[AutoClaim.L2ToLxBridgeDetector] +Enabled = false +StartL1Block = 0 +PollInterval = "3s" + +[AutoClaim.BridgeServiceFinder] +RollupManagerAddr = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" +PollInterval = "3s" + +[AutoClaim.BridgeServiceFinder.BridgeURLs] +1 = "http://aggkit-001:5577" +2 = "http://aggkit-002:5577" + +[[AutoClaim.Claimers]] +Enabled = false +ID = "autoclaim-001" +NetworkType = "EVM" +NetworkID = 1 +URLRPC = "http://l2-anvil-001:8545" +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" +PolicyName = "allow-all" +GasOffset = 100000 +WaitPeriod = "1s" +RetryAfter = "1s" +MaxRetries = 180 + +[AutoClaim.Claimers.Policy] +AllowMessageClaims = false +AllowedOrigins = [] +AllowedTokens = [] +ManualFallback = false +MaxGas = 500000 + +[AutoClaim.Claimers.EthTxManager] +FrequencyToMonitorTxs = "1s" +WaitTxToBeMined = "2s" +WaitReceiptMaxTime = "250ms" +WaitReceiptCheckInterval = "1s" +PrivateKeys = [ + {Method = "local", Path = "/etc/aggkit/aggoracle.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m"}, +] +ForcedGas = 0 +GasPriceMarginFactor = 1 +MaxGasPriceLimit = 0 +StoragePath = "/tmp/ethtxmanager-autoclaim.sqlite" +ReadPendingL1Txs = false +SafeStatusL1NumberOfBlocks = 0 +FinalizedStatusL1NumberOfBlocks = 0 +EstimateGasMaxRetries = 1 + +[AutoClaim.Claimers.EthTxManager.Etherman] +URL = "http://l2-anvil-001:8545" +MultiGasProvider = false +L1ChainID = 20201 +HTTPHeaders = {} diff --git a/test/e2e/envs/anvil-2chains/config/001/aggoracle.keystore b/test/e2e/envs/anvil-2chains/config/001/aggoracle.keystore new file mode 100644 index 000000000..9e0b74de2 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/001/aggoracle.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "5ec2fbc61368fbab89b6e864753e0a8b" + }, + "ciphertext": "8a09b2e806d443ddb7575e15628133da3dd463a6f66e6398024226b7a4063b63", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "9639bdb4596bae41fdd20986234448ff6f0640cbb52a91fee96e8f5bf552b8fe" + }, + "mac": "46b3edb02407e54719aea1726c27af272c40953e1a4ef1e1f0676cc2285d30a5" + }, + "id": "d3091e5f-d122-44cb-b47a-308fb39790a4", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/001/sequencer.keystore b/test/e2e/envs/anvil-2chains/config/001/sequencer.keystore new file mode 100644 index 000000000..e09c8c122 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/001/sequencer.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "5bf47bbafe1b518910544614a43eec8c" + }, + "ciphertext": "0a8abf1ce90945350f6a14356b79b15d69eec9e77d4357e500147ce1c0718e82", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "94913f125b1d4f76cc1d010b0567e30cd5966e561abba9d8944be063b4345b50" + }, + "mac": "f3c73279efd74598e00cc2fe5ee8a52321920d564bf92da815d4053e263a02bc" + }, + "id": "4242e2de-a97c-4f0e-84c1-d119a62595a2", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/001/sovereignadmin.keystore b/test/e2e/envs/anvil-2chains/config/001/sovereignadmin.keystore new file mode 100644 index 000000000..88da90699 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/001/sovereignadmin.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "c75071241b98d74fb8a5aa8aa386d8e5" + }, + "ciphertext": "ea7a83722729a2f2c3c557773c74ab7bd30e59bb6faff3c67334fbb01486fd40", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "53596bb73362ca70fa6f9b4e5ff57ca2ea7a6f1448dafb2c763ebd8668ecdf83" + }, + "mac": "f5a216e073d175068d3bcc1901cf9e573bd05540ffef1d03072ecbee339f0967" + }, + "id": "1dfb1229-60c2-4b5f-99a0-c5ddbe7341b2", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/002/aggkit-config.toml b/test/e2e/envs/anvil-2chains/config/002/aggkit-config.toml new file mode 100644 index 000000000..c415691c8 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/002/aggkit-config.toml @@ -0,0 +1,769 @@ +# ============================================================================== +# _ ____ ____ _ _____ _____ +# / \ / ___|/ ___| |/ /_ _|_ _| +# / _ \| | _| | _| ' / | | | | +# / ___ \ |_| | |_| | . \ | | | | +# /_/ \_\____|\____|_|\_\___| |_| +# +# This is a reference config file used by the Kurtosis CDK Testing +# setup. The values here should work, but are necessarily meant for +# production environments. DYOR +# The below configs are the default mandatory parameters to be used. +# ============================================================================== + +PathRWData = "/tmp" +L1URL = "http://anvil-001:8545" +L2URL = "http://l2-anvil-002:8545" +OpNodeURL = "http://l2-anvil-002:8545" + +# Check if agglayer grpc or readrpc should be used for AggLayerURL +AggLayerURL = "http://agglayer:4443" + +AggchainProofURL= "aggkit-prover-002:4446" +SequencerPrivateKeyPath = "/etc/aggkit/sequencer.keystore" +SequencerPrivateKeyPassword = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" +RPCURL = "http://l2-anvil-002:8545" + +# These values can be overridden directly from genesis.json +rollupCreationBlockNumber = "60" +rollupManagerCreationBlockNumber = "60" +genesisBlockNumber = "60" +# ------------------------------------------------------------------------------ + +# ============================================================================== +# _ _ ____ ___ _ _ _____ ___ ____ +# | | / |/ ___/ _ \| \ | | ___|_ _/ ___| +# | | | | | | | | | \| | |_ | | | _ +# | |___| | |__| |_| | |\ | _| | | |_| | +# |_____|_|\____\___/|_| \_|_| |___\____| +# +# ------------------------------------------------------------------------------ +[L1Config] +# ------------------------------------------------------------------------------ +# URL is the L1 network url +# ------------------------------------------------------------------------------ +URL = "http://anvil-001:8545" + +# ------------------------------------------------------------------------------ +# L1 chain id +# ------------------------------------------------------------------------------ +chainId = "271828" + +# ------------------------------------------------------------------------------ +# Address of the zkevm global exit root contract on L1 +# ------------------------------------------------------------------------------ +polygonZkEVMGlobalExitRootAddress = "0x1f7ad7caA53e35b4f0D138dC5CBF91aC108a2674" + +# ------------------------------------------------------------------------------ +# Address of the rollup manager contract on L1 +# ------------------------------------------------------------------------------ +polygonRollupManagerAddress = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" + +# ------------------------------------------------------------------------------ +# Address of the pol token address on L1 +# ------------------------------------------------------------------------------ +polTokenAddress = "0xEdE9cf798E0fE25D35469493f43E88FeA4a5da0E" + +# ------------------------------------------------------------------------------ +# Address of the sovereign rollup contract on L2 +# ------------------------------------------------------------------------------ +polygonZkEVMAddress = "0x5D1A491A416feEbf8C123A558ec28A239960bd0E" + +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +# ============================================================================== +# _ ____ ____ ___ _ _ _____ ___ ____ +# | | |___ \ / ___/ _ \| \ | | ___|_ _/ ___| +# | | __) | | | | | | \| | |_ | | | _ +# | |___ / __/| |__| |_| | |\ | _| | | |_| | +# |_____|_____|\____\___/|_| \_|_| |___\____| +# +# ------------------------------------------------------------------------------ +[L2Config] +# ------------------------------------------------------------------------------ +# Address of the sovereign global exit root proxy contract on L2 +# ------------------------------------------------------------------------------ +GlobalExitRootAddr = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +# ============================================================================== +# _ ___ ____ +# | | / _ \ / ___| +# | | | | | | | _ +# | |__| |_| | |_| | +# |_____\___/ \____| +# +# ------------------------------------------------------------------------------ +[Log] +# ------------------------------------------------------------------------------ +# Environment generally dictates the format of the logs and the +# sampling rate. We often default to production even for development +# because of the JSON encoding. +# +# https://github.com/uber-go/zap/blob/a55bdc32f526699c3b4cc51a2cc97e944d02fbbf/config.go#L120 +# https://github.com/uber-go/zap/blob/a55bdc32f526699c3b4cc51a2cc97e944d02fbbf/config.go#L161 +# ------------------------------------------------------------------------------ +Environment = "development" + +# ------------------------------------------------------------------------------ +# Level determines the log level that will be written to the +# log. Generally we'll switch to debug if we want to troubleshoot +# something specifically otherwise we leave it at info +# ------------------------------------------------------------------------------ +Level = "info" + +# ------------------------------------------------------------------------------ +# Outputs define the output paths for writing logs. The default is to +# write to stderr, but other output paths should be supported +# +# https://github.com/uber-go/zap/blob/a55bdc32f526699c3b4cc51a2cc97e944d02fbbf/writer.go#L32-L50 +# ------------------------------------------------------------------------------ +Outputs = ["stderr"] + +# ============================================================================== +# ____ ____ ____ +# | _ \| _ \ / ___| +# | |_) | |_) | | +# | _ <| __/| |___ +# |_| \_\_| \____| +# +# ------------------------------------------------------------------------------ +[RPC] +# ------------------------------------------------------------------------------ +# Port will configure the port that the JSON RPC server will listen on +# ------------------------------------------------------------------------------ +Port = "5576" + +# ============================================================================== +# ____ _____ ____ _____ +# | _ \| ____/ ___|_ _| +# | |_) | _| \___ \ | | +# | _ <| |___ ___) || | +# |_| \_\_____|____/ |_| +# +# ------------------------------------------------------------------------------ +[PublicREST] +# ------------------------------------------------------------------------------ +# Port will configure the port that the REST API HTTP server will +# listen on +# ------------------------------------------------------------------------------ +Port = "5577" + +[AdminREST] +Port = 5579 + +# ============================================================================== +# _ ____ ____ ____ _____ _ _ ____ _____ ____ +# / \ / ___|/ ___/ ___|| ____| \ | | _ \| ____| _ \ +# / _ \| | _| | _\___ \| _| | \| | | | | _| | |_) | +# / ___ \ |_| | |_| |___) | |___| |\ | |_| | |___| _ < +# /_/ \_\____|\____|____/|_____|_| \_|____/|_____|_| \_\ +# +# ------------------------------------------------------------------------------ +[AggSender] +# ------------------------------------------------------------------------------ +# StoragePath is the path of the sqlite db for the AggSender to store the data +# ------------------------------------------------------------------------------ +# StoragePath = "/tmp" + +# ------------------------------------------------------------------------------ +# AggsenderPrivateKey is the private key which is used to sign certificates +# ------------------------------------------------------------------------------ +AggSenderPrivateKey = {Path = "/etc/aggkit/sequencer.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m"} + +# ------------------------------------------------------------------------------ +# Defines if a check on aggsender proposer startup will be performed +# to see if the proposer is in the multisig committee +# ------------------------------------------------------------------------------ +# RequireCommitteeMembershipCheck = false +Mode = "PessimisticProof" +CheckStatusCertificateInterval = "1s" + +# ------------------------------------------------------------------------------ +# TriggerCertMode is the mode used to trigger certificate sending +# Valid values are: "EpochBased", "NewBridge", "ASAP", "Auto" +# EpochBased: this is the legacy mode that waits until reach a percentage of a epoch (you can configure here: AggSender.TriggerEpochBased) +# ASAP: this mode try to generate a new certificate after a successful settled certificate +# NewBridge: Each time that a new bridge is done in L2 it generate a certificate (if it's possible) (experimental) +# ------------------------------------------------------------------------------ +TriggerCertMode = "ASAP" + +# # Encouraged to use the default values - the parameters are left commented out for reference +# +# [AggSender.TriggerASAP] +# # Delay between the moment the aggsender becomes idle and when it sends a new certificate trigger +# DelayBetweenCertificates = "1s" +# # Minimum time that must elapse between certificate generation triggers, regardless of the trigger source +# MinimumNewCertificateInterval = "5s" +# # When enabled, the ASAP trigger will automatically generate certificate triggers when new bridge events are detected on L2 +# OnNewL2Bridge = false +# + +# ------------------------------------------------------------------------------ +# MaxCertSize is the maximum size of the certificate +# i.e (the emitted certificate cannot be bigger that this size) +# 0 is infinite +# ------------------------------------------------------------------------------ +# MaxCertSize = 0 +[AggSender.ValidatorClient] +URL = "aggkit-validator-002:5578" + +[AggSender.AggkitProverClient] +UseTLS = false + +# ------------------------------------------------------------------------------ +# URLRPCL2 is the URL of the L2 RPC node +# ------------------------------------------------------------------------------ +# URLRPCL2 = "http://l2-anvil-002:8545" + +# ------------------------------------------------------------------------------ +# EpochNotificationPercentage indicates the percentage of the epoch +# the AggSender should send the certificate +# 0 -> Begin +# 50 -> Middle +# ------------------------------------------------------------------------------ +# EpochNotificationPercentage = 50 + +# ------------------------------------------------------------------------------ +# MaxRetriesStoreCertificate is the maximum number of retries to store a certificate +# 0 is infinite +# ------------------------------------------------------------------------------ +# MaxRetriesStoreCertificate = 3 + +# ------------------------------------------------------------------------------ +# DelayBetweenRetries is the delay between retries +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# ------------------------------------------------------------------------------ +# DelayBetweenRetries = 5s + +# ------------------------------------------------------------------------------ +# BridgeMetadataAsHash is a flag to import the bridge metadata as hash +# ------------------------------------------------------------------------------ +# BridgeMetadataAsHash = false + +# ------------------------------------------------------------------------------ +# DryRun is a flag to enable the dry run mode +# in this mode the AggSender will not send the certificates to Agglayer +# ------------------------------------------------------------------------------ +# DryRun = false + +# ------------------------------------------------------------------------------ +# EnableRPC is a flag to enable the RPC for aggsender +# ------------------------------------------------------------------------------ +# EnableRPC = false + +[AggSender.AgglayerClient] + +[[AggSender.AgglayerClient.APIRateLimits]] +MethodName = "SendCertificate" + +[AggSender.AgglayerClient.APIRateLimits.RateLimit] +# Disable limit +NumRequests = 0 + +[AggSender.AgglayerClient.GRPC] +URL = "http://agglayer:4443" +MinConnectTimeout = "5s" +RequestTimeout = "300s" +UseTLS = false + +[AggSender.AgglayerClient.GRPC.Retry] +InitialBackoff = "1s" +MaxBackoff = "10s" +BackoffMultiplier = 2.0 +MaxAttempts = 20 + +[AggSender.StorageRetainCertificatesPolicy] +# ------------------------------------------------------------------------------ +# RetainCertificatesCount controls how many certificates to retain on storage. +# If set to zero, all certificates will be stored. +# ------------------------------------------------------------------------------ +# RetainCertificatesCount = 0 + +# ------------------------------------------------------------------------------ +# KeepCertificatesHistory is a flag to keep the certificates history on storage +# ------------------------------------------------------------------------------ +# KeepCertificatesHistory = true + +# ------------------------------------------------------------------------------ +# RetryCertAfterInError when a cert pass to 'InError'state +# the AggSender will try to resend it immediately +# ------------------------------------------------------------------------------ +# RetryCertAfterInError = false + +# ------------------------------------------------------------------------------ +# RequireNoFEPBlockGap is true if the AggSender should not accept a gap between +# lastBlock from lastCertificate and first block of FEP +# ------------------------------------------------------------------------------ +# RequireNoFEPBlockGap = false + +# ------------------------------------------------------------------------------ +# RequireOneBridgeInPPCertificate is a flag to force the AggSender to have at least one bridge exit +# for the Pessimistic Proof certificates +# ------------------------------------------------------------------------------ +# RequireOneBridgeInPPCertificate = false + +# ------------------------------------------------------------------------------ +# MaxL2BlockNumber is the last L2 block number that is going to be included in a certificate +# 0 means disabled +# ------------------------------------------------------------------------------ +# MaxL2BlockNumber = 0 + +# ------------------------------------------------------------------------------ +# StopOnFinishedSendingAllCertificates is a flag to stop the AggSender when it finishes sending all certificates +# up to MaxL2BlockNumber +# ------------------------------------------------------------------------------ +# StopOnFinishedSendingAllCertificates = false + +# ============================================================================== +# _ ____ ____ ___ ____ _ ____ _ _____ +# / \ / ___|/ ___|/ _ \| _ \ / \ / ___| | | ____| +# / _ \| | _| | _| | | | |_) | / _ \| | | | | _| +# / ___ \ |_| | |_| | |_| | _ < / ___ \ |___| |___| |___ +# /_/ \_\____|\____|\___/|_| \_\/_/ \_\____|_____|_____| +# +# ------------------------------------------------------------------------------ +[AggOracle] +# ------------------------------------------------------------------------------ +# TargetChainType currently only supports "EVM" +# ------------------------------------------------------------------------------ +# TargetChainType = "EVM" + +# ------------------------------------------------------------------------------ +# URLRPCL1 is the URL of the L1 RPC node +# ------------------------------------------------------------------------------ +# URLRPCL1 = "http://anvil-001:8545" + +# ------------------------------------------------------------------------------ +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# A3 measurement (plans/snapshot-v2-aggkit-e2e/A3-evidence/): this poll +# period is the single largest lever found for this env's measured +# TestMain post-test bridge check. At the prior 10s value (which matches +# this repo's own config/default.go template default -- not a regression, +# just conservative), the L1->L2 GER-injection wait showed a bimodal +# ~36s/~43s split across otherwise-identical runs depending on poll-cycle +# alignment; at 1s it collapsed to a tight ~35-37s cluster (5 consecutive +# runs, spread <2s). Lowering only changes how often AggOracle checks L1 +# for a new GER to inject -- it does not touch any finality/reorg-safety +# knob, so this is not a raciness trade-off. +# ------------------------------------------------------------------------------ +WaitPeriodNextGER = "1s" + +# ------------------------------------------------------------------------------ +# +# ------------------------------------------------------------------------------ +EnableAggOracleCommittee = false + +[AggOracle.EVMSender] +# ------------------------------------------------------------------------------ +# Address of the sovereign global exit root proxy contract on L2 +# ------------------------------------------------------------------------------ +GlobalExitRootL2 = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" + +# ------------------------------------------------------------------------------ +# URLRPCL2 is the URL of the L2 RPC node +# ------------------------------------------------------------------------------ +# URLRPCL2 = "http://l2-anvil-002:8545" + +# ------------------------------------------------------------------------------ +# GasOffset is the gas to add on the estimated gas when sending the claim txs +# ------------------------------------------------------------------------------ +# GasOffset = 0 + +# ------------------------------------------------------------------------------ +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# A3 measurement: restores this repo's own config/default.go template +# default (1s) -- the prior 10s value here was 10x slower than upstream's +# own default, not an intentional devnet choice. Measured with +# WaitPeriodNextGER alone at 1s (WaitPeriodMonitorTx still 10s) this did +# NOT collapse the bimodal split on its own (median stayed ~43s); it is +# kept at the default anyway since it costs nothing and is the correct +# baseline value. +# ------------------------------------------------------------------------------ +WaitPeriodMonitorTx = "1s" + +# ------------------------------------------------------------------------------ +# +# ------------------------------------------------------------------------------ + +[AggOracle.EVMSender.EthTxManager] +# ------------------------------------------------------------------------------ +# PrivateKeys defines all the key store files that are going +# to be read in order to provide the private keys to sign the L1 txs +# ------------------------------------------------------------------------------ +PrivateKeys = [{Path = "/etc/aggkit/aggoracle.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m"}] + +# ------------------------------------------------------------------------------ +# FrequencyToMonitorTxs frequency of the resending failed txs +# Duration expressed in units: [ns, us, ms, s, m, h, d]" +# ------------------------------------------------------------------------------ +# FrequencyToMonitorTxs = "1s" + +# ------------------------------------------------------------------------------ +# WaitTxToBeMined time to wait after transaction was sent to the ethereum +# ------------------------------------------------------------------------------ +# WaitTxToBeMined = "2s" + +# ------------------------------------------------------------------------------ +# GetReceiptMaxTime is the max time to wait to get the receipt of the mined transaction +# ------------------------------------------------------------------------------ +# GetReceiptMaxTime = "250ms" + +# ------------------------------------------------------------------------------ +# GetReceiptWaitInterval is the time to sleep before trying to get the receipt of the mined transaction +# ------------------------------------------------------------------------------ +# GetReceiptWaitInterval = "1s" + +# ------------------------------------------------------------------------------ +# ForcedGas is the amount of gas to be forced in case of gas estimation error +# ------------------------------------------------------------------------------ +# ForcedGas = 0 + +# ------------------------------------------------------------------------------ +# GasPriceMarginFactor is used to multiply the suggested gas price provided by the network +# in order to allow a different gas price to be set for all the transactions and making it +# easier to have the txs prioritized in the pool, default value is 1. +# +# example: +# suggested gas price: 100 +# GasPriceMarginFactor: 1 +# gas price = 100 +# +# suggested gas price: 100 +# GasPriceMarginFactor: 1.1 +# gas price = 110 +# ------------------------------------------------------------------------------ +# GasPriceMarginFactor = 1 + +# ------------------------------------------------------------------------------ +# MaxGasPriceLimit helps avoiding transactions to be sent over an specified +# gas price amount, default value is 0, which means no limit. +# If the gas price provided by the network and adjusted by the GasPriceMarginFactor +# is greater than this configuration, transaction will have its gas price set to +# the value configured in this config as the limit. +# +# example: +# suggested gas price: 100 +# gas price margin factor: 20% +# max gas price limit: 150 +# tx gas price = 120 +# +# suggested gas price: 100 +# gas price margin factor: 20% +# max gas price limit: 110 +# tx gas price = 110 +# ------------------------------------------------------------------------------ +# MaxGasPriceLimit = 0 + +# ------------------------------------------------------------------------------ +# StoragePath is the path of the internal storage +# ------------------------------------------------------------------------------ +# StoragePath = "/tmp/ethtxmanager-aggoracle.sqlite" + +# ------------------------------------------------------------------------------ +# ReadPendingL1Txs is a flag to enable the reading of pending L1 txs +# It can only be enabled if DBPath is empty +# ------------------------------------------------------------------------------ +# ReadPendingL1Txs = false + +# ------------------------------------------------------------------------------ +# SafeStatusL1NumberOfBlocks overwrites the number of blocks to consider a tx as safe +# overwriting the default value provided by the network +# 0 means that the default value will be used +# ------------------------------------------------------------------------------ +# SafeStatusL1NumberOfBlocks = 5 + +# ------------------------------------------------------------------------------ +# FinalizedStatusL1NumberOfBlocks overwrites the number of blocks to +# consider a tx as finalized overwriting the default value provided by the network +# 0 means that the default value will be used +# ------------------------------------------------------------------------------ +# FinalizedStatusL1NumberOfBlocks = 10 + +[AggOracle.EVMSender.EthTxManager.Etherman] +# ------------------------------------------------------------------------------ +# Needs to be set to be the sovereign L2 chain id +# ------------------------------------------------------------------------------ +L1ChainID = "20202" + +# ============================================================================== +# ____ ____ ___ ____ ____ _____ _ ____ ______ ___ _ ____ +# | __ )| _ \|_ _| _ \ / ___| ____| | |___ \/ ___\ \ / / \ | |/ ___| +# | _ \| |_) || || | | | | _| _| | | __) \___ \\ V /| \| | | +# | |_) | _ < | || |_| | |_| | |___| |___ / __/ ___) || | | |\ | |___ +# |____/|_| \_\___|____/ \____|_____|_____|_____|____/ |_| |_| \_|\____| +# ------------------------------------------------------------------------------ +[BridgeL2Sync] +# ------------------------------------------------------------------------------ +# BridgeAddr is the address of the sovereign bridge contract on L2 +# ------------------------------------------------------------------------------ +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +# ------------------------------------------------------------------------------ +# BlockFinality selects which L2 block type to use when querying the bridge +# contract for synchronization +# Accepted values: +# - LatestBlock +# - SafeBlock +# - PendingBlock +# - FinalizedBlock +# ------------------------------------------------------------------------------ +BlockFinality = "LatestBlock" + +[ReorgDetectorL2] +# ------------------------------------------------------------------------------ +# FinalizedBlock defines which L2 block tag is used as the source of truth when +# checking for reorgs on L2 +# Accepted values: +# - LatestBlock +# - SafeBlock +# - PendingBlock +# - FinalizedBlock +# +# cdk-erigon sovereign chains never advance the "finalized" (nor "safe") block +# tag past genesis, so the reorg detector never drops finalized blocks from its +# tracked set. That set then grows without bound and the detector re-queries a +# header for every tracked block on each check tick; under constrained CI CPU +# this starves the L2 bridge syncer's downloader and the sync silently stalls, +# so L2->L1 bridge claims never become ready. op-stack chains advance +# "finalized" through op-node, and anvil advances it through +# --slots-in-an-epoch; both keep the default tag. Track the latest block for +# cdk-erigon so the tracked set stays bounded (matches BridgeL2Sync). +# ------------------------------------------------------------------------------ +FinalizedBlock = "FinalizedBlock" + +# ------------------------------------------------------------------------------ +# DBPath path of the sqlite db +# ------------------------------------------------------------------------------ +# DBPath = "/tmp/bridgel2sync.sqlite" + +# ------------------------------------------------------------------------------ +# First block that will be queried when starting the synchronization from scratch. +# It should be a number equal or bellow the creation of the bridge contract +# ------------------------------------------------------------------------------ +# InitialBlockNum = 0 + +# ------------------------------------------------------------------------------ +# The amount of blocks that will be queried to the client on each request +# ------------------------------------------------------------------------------ +# SyncBlockChunkSize = 100 + +# ------------------------------------------------------------------------------ +# The time that will be waited when an unexpected error happens before retry +# ------------------------------------------------------------------------------ +# RetryAfterErrorPeriod = "1s" + +# ------------------------------------------------------------------------------ +# The maximum number of consecutive attempts that will happen before panic. +# Any number smaller than zero will be considered as unlimited retries +# ------------------------------------------------------------------------------ +# MaxRetryAttemptsAfterError = -1 + +# ------------------------------------------------------------------------------ +# Time that will be waited when the synchronizer has reached the latest block +# ------------------------------------------------------------------------------ +# WaitForNewBlocksPeriod = "3s" + +# ============================================================================== +# _ _ ___ _ _ _____ ___ _____ ____ _____ _____ ______ ___ _ ____ +# | | / |_ _| \ | | ___/ _ \_ _| _ \| ____| ____/ ___\ \ / / \ | |/ ___| +# | | | || || \| | |_ | | | || | | |_) | _| | _| \___ \\ V /| \| | | +# | |___| || || |\ | _|| |_| || | | _ <| |___| |___ ___) || | | |\ | |___ +# |_____|_|___|_| \_|_| \___/ |_| |_| \_\_____|_____|____/ |_| |_| \_|\____| +# +# ------------------------------------------------------------------------------ +[L1InfoTreeSync] +# ------------------------------------------------------------------------------ +# The initial block number from which to start syncing. +# Default: 0 +# ------------------------------------------------------------------------------ +InitialBlock = "60" + +# ============================================================================== +# _ ____ ____ _____ ____ ______ ___ _ ____ +# | | |___ \ / ___| ____| _ \/ ___\ \ / / \ | |/ ___| +# | | __) | | _| _| | |_) \___ \\ V /| \| | | +# | |___ / __/| |_| | |___| _ < ___) || | | |\ | |___ +# |_____|_____|\____|_____|_| \_\____/ |_| |_| \_|\____| +# ============================================================================== +[L2GERSync] +# ------------------------------------------------------------------------------ +# BlockFinality indicates which finality follows AggLayer accepted values are: +# LatestBlock, SafeBlock, PendingBlock, FinalizedBlock, EarliestBlock +# Default value is "LatestBlock" +# ------------------------------------------------------------------------------ +BlockFinality = "LatestBlock" + +# ============================================================================== +# _ _ __ +# __ _ __ _ __ _ ___| |__ __ _(_)_ __ _ __ _ __ ___ ___ / _| __ _ ___ _ __ +# / _` |/ _` |/ _` |/ __| '_ \ / _` | | '_ \| '_ \| '__/ _ \ / _ \| |_ / _` |/ _ \ '_ \ +#| (_| | (_| | (_| | (__| | | | (_| | | | | | |_) | | | (_) | (_) | _| (_| | __/ | | | +# \__,_|\__, |\__, |\___|_| |_|\__,_|_|_| |_| .__/|_| \___/ \___/|_| \__, |\___|_| |_| +# |___/ |___/ |_| |___/ +# ------------------------------------------------------------------------------ +[AggchainProofGen] +# ------------------------------------------------------------------------------ +# SovereignRollupAddr is the address of the sovereign rollup contract on L1 +# ------------------------------------------------------------------------------ +SovereignRollupAddr = "0x5D1A491A416feEbf8C123A558ec28A239960bd0E" + +# ------------------------------------------------------------------------------ +# GlobalExitRootL2Addr is the address of the GlobalExitRootManager contract on l2 sovereign chain +# this address is needed for the AggchainProof mode of the AggSender +# ------------------------------------------------------------------------------ +GlobalExitRootL2 = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" + +[AggchainProofGen.AggkitProverClient] +# ------------------------------------------------------------------------------ +# UseTLS is a flag to enable the AggkitProver TLS handshake in the AggSender-AggkitProver gRPC connection +# ------------------------------------------------------------------------------ +# UseTLS = false + +# ============================================================================== +# ____ __ _ _ _ +# | _ \ _ __ ___ / _(_) (_)_ __ __ _ +# | |_) | '__/ _ \| |_| | | | '_ \ / _` | +# | __/| | | (_) | _| | | | | | | (_| | +# |_| |_| \___/|_| |_|_|_|_| |_|\__, | +# |___/ +# ------------------------------------------------------------------------------ +[Profiling] +# ------------------------------------------------------------------------------ +# ProfilingHost is the address to bind the profiling server +# Default: "localhost" +# ------------------------------------------------------------------------------ +ProfilingHost = "0.0.0.0" + +# ------------------------------------------------------------------------------ +# ProfilingPort is the port to bind the profiling server +# Default: 6060 +# ------------------------------------------------------------------------------ +ProfilingPort = 6060 + +# ------------------------------------------------------------------------------ +# ProfilingEnabled is the flag to enable/disable the profiling server +# Default: false +# ------------------------------------------------------------------------------ +ProfilingEnabled = true + + +# https://github.com/agglayer/aggkit/pull/744 +[Validator] +EnableRPC = true +# Signer = { Method = "mock" } +Signer = { Method = "local", Path = "/etc/aggkit/sequencer.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" } +# PessimisticProof or AggchainProof +Mode = "PessimisticProof" + +[Validator.ServerConfig] +Host = "0.0.0.0" +Port = 5578 +MaxDecodingMessageSize = 1073741824 # 1GB + +# Is it necessary to specify all of these values again? +[Validator.LerQuerierConfig] +RollupManagerAddr = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" +RollupCreationBlockL1 = "60" + +[Validator.AgglayerClient] +Cached = true + +[Validator.AgglayerClient.ConfigurationCache] +TTL = "15m" +Capacity = 100 + +[Validator.AgglayerClient.GRPC] +URL = "http://agglayer:4443" +UseTLS = false + +# ============================================================================== +# _ _____ ___ ____ _ _ ___ __ __ +# / \ _ _ |_ _/ _ \ / ___| | / \ |_ _| \/ | +# / _ \ | | | | || | | || | | | / _ \ | || |\/| | +# / ___ \|_| | | || |_| || |___| |___ / ___ \ | || | | | +# /_/ \_\__,_| |_| \___/ \____|_____/_/ \_\___|_| |_| +# +# ------------------------------------------------------------------------------ +# Auto Claim is entirely absent from this template's defaults (the aggkit +# binary's own config/default.go supplies working, inert defaults -- +# Claimers = [], L2ToLxBridgeDetector.Enabled = false -- when this section is +# omitted). This block only renders when aggkit_autoclaim_enabled is true, +# i.e. when this instance's own l2_network_id is listed in +# aggkit_autoclaim_destinations (see input_parser.star). It configures a +# SINGLE claimer targeting this instance's own destination network -- never +# network 0 (L1); deposits destined to L1 must stay manual-claim-only for the +# bridge UI. E2E tests enable the required detectors and claimer explicitly; +# keeping them disabled in the baseline prevents unrelated stateful scenarios +# from racing AutoClaim. +# Requires "autoclaim" to also be listed in aggkit_components (see +# _log_autoclaim_warning in aggkit.star). +# ------------------------------------------------------------------------------ +[AutoClaim] +DryRun = false +StoragePath = "/tmp/autoclaim.sqlite" + +[AutoClaim.API] +Enabled = false + +[AutoClaim.L1ToL2BridgeDetector] +Enabled = false +StartBlock = 0 +PollInterval = "3s" +EtrogL1UpgradeBlock = 0 + +[AutoClaim.L2ToLxBridgeDetector] +Enabled = false +StartL1Block = 0 +PollInterval = "3s" + +[AutoClaim.BridgeServiceFinder] +RollupManagerAddr = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" +PollInterval = "3s" + +[AutoClaim.BridgeServiceFinder.BridgeURLs] +1 = "http://aggkit-001:5577" +2 = "http://aggkit-002:5577" + +[[AutoClaim.Claimers]] +Enabled = false +ID = "autoclaim-002" +NetworkType = "EVM" +NetworkID = 2 +URLRPC = "http://l2-anvil-002:8545" +BridgeAddr = "0xC8cbEBf950B9Df44d987c8619f092beA980fF038" +PolicyName = "allow-all" +GasOffset = 100000 +WaitPeriod = "1s" +RetryAfter = "1s" +MaxRetries = 180 + +[AutoClaim.Claimers.Policy] +AllowMessageClaims = false +AllowedOrigins = [] +AllowedTokens = [] +ManualFallback = false +MaxGas = 500000 + +[AutoClaim.Claimers.EthTxManager] +FrequencyToMonitorTxs = "1s" +WaitTxToBeMined = "2s" +WaitReceiptMaxTime = "250ms" +WaitReceiptCheckInterval = "1s" +PrivateKeys = [ + {Method = "local", Path = "/etc/aggkit/aggoracle.keystore", Password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m"}, +] +ForcedGas = 0 +GasPriceMarginFactor = 1 +MaxGasPriceLimit = 0 +StoragePath = "/tmp/ethtxmanager-autoclaim.sqlite" +ReadPendingL1Txs = false +SafeStatusL1NumberOfBlocks = 0 +FinalizedStatusL1NumberOfBlocks = 0 +EstimateGasMaxRetries = 1 + +[AutoClaim.Claimers.EthTxManager.Etherman] +URL = "http://l2-anvil-002:8545" +MultiGasProvider = false +L1ChainID = 20202 +HTTPHeaders = {} diff --git a/test/e2e/envs/anvil-2chains/config/002/aggoracle.keystore b/test/e2e/envs/anvil-2chains/config/002/aggoracle.keystore new file mode 100644 index 000000000..557813d07 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/002/aggoracle.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "249b2d77f2f8b3d7b8ef9b9b9c9c1630" + }, + "ciphertext": "11b1cc93393e73474663253775b2ddc88d48622d69850f7bb3f3290542e24a9c", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "ce7cfd692a21f4b095282ff564aff6d971d537837b8601700eabce3f5716e04e" + }, + "mac": "33afcba5bb6343aa9f8e081540fc8797c491d75f8d09ba8b0b3f420ec5b9a50d" + }, + "id": "c7afce59-d87e-4d78-8975-2e14b76b9638", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/002/sequencer.keystore b/test/e2e/envs/anvil-2chains/config/002/sequencer.keystore new file mode 100644 index 000000000..1b7808d4a --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/002/sequencer.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "38dad6dd09ad368977d1ad9f22a16e92" + }, + "ciphertext": "4f14eaed64efeec70863a09bca758270cdcdfcf5ed7ebd65d504e891c5ba7451", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "f3f71b8b380a19cef8b0ddc57bb5318041db90150722ac2931e2d9702c9a8155" + }, + "mac": "43f945d2d9f9527568a114f07ccec39cb223b176f38e6e4378f1e779ca5ccb12" + }, + "id": "60e29f01-3a3a-4abf-8afd-857ca51c6792", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/002/sovereignadmin.keystore b/test/e2e/envs/anvil-2chains/config/002/sovereignadmin.keystore new file mode 100644 index 000000000..5ac6b4ee9 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/002/sovereignadmin.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "7c33c59b56cc88c5a8703afeff6b720b" + }, + "ciphertext": "9a48a6c3810bee9c8c085340a81f366bc76c82bad9f9ee74f71fec5987463c84", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "14c1b9666123c384f92fb992ca253d4b9ddfe4840e96e1fd1fd1e7ea6d7dfbba" + }, + "mac": "1762a85728a35c17857e6e21d564f4397c02fb7cdedcb4e5300f544c7a7637a9" + }, + "id": "a677ead1-5159-4697-b8a0-6dc940f4b98f", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/aggkit-proxy/aggkit-proxy.toml b/test/e2e/envs/anvil-2chains/config/aggkit-proxy/aggkit-proxy.toml new file mode 100644 index 000000000..fad151569 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/aggkit-proxy/aggkit-proxy.toml @@ -0,0 +1,105 @@ +# aggkit-proxy config (proxy + tracker components; --components=proxy,tracker +# -- see aggkit_proxy.star). +# +# Values chosen to mirror aggkit's own +# proxy/scripts/configuration_based_on_kurtosis.sh recipe for a local devnet +# (LatestBlock instead of FinalizedBlock -- FinalizedBlock lags too much on a +# local devnet; PollInterval 10s instead of the 30s upstream default; +# HealthCheckPath "/" since the aggkit bridge REST service serves its health +# check at the root path, not "/health"). [Tracker] mirrors the binary's own +# defaults (proxy/config/default.go) except RetentionPeriod, raised so a slow +# L2->L1 demo certificate stays inspectable through /tracker/v1. BridgeAddrs is +# intentionally left unset -- the tracker's own on-chain discovery covers this +# package's needs there (an absent entry still matches logs on the event +# signature alone). L1GlobalExitRootAddress has NO such fallback (confirmed in +# aggkit's bridgetracker/sources/ger.go): left unset it defaults to the zero +# address, which permanently stalls StepWaitingGERUpdate for every L1->L2 +# bridge, so it must be set explicitly below. As of aggkit v0.11.0-rc5 +# (bridgetracker/config.go's Config.Validate, PR agglayer/aggkit#1784) the +# proxy now fails fast at startup with a clear error instead of silently +# stalling if this resolves to the zero address -- this package's templating +# already supplies a real address, so the new check is expected to pass +# without any config change here. + +[Log] +Environment = "development" +Level = "info" +Outputs = ["stderr"] + +[L1RPC] +URL = "http://anvil-001:8545" +Mode = "basic" +RetryMode = "backoff" +MaxRetries = 5 +InitialBackoff = "2s" +MaxBackoff = "10s" +BackoffMultiplier = 2.0 + +[BridgeServiceFinder] +RollupManagerAddr = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" +BlockFinality = "LatestBlock" +PollInterval = "10s" +BlockChunkSize = 10000 +HealthCheckPath = "/" +HealthCheckTimeout = "5s" +RequireAllHealthyOnStart = false + +[BridgeServiceFinder.BridgeURLs] +0 = "http://aggkit-001:5577" +1 = "http://aggkit-001:5577" +2 = "http://aggkit-002:5577" + +[BridgeServiceFinder.RPCURLs] +0 = "http://anvil-001:8545" +1 = "http://l2-anvil-001:8545" +2 = "http://l2-anvil-002:8545" + +[REST] +Host = "0.0.0.0" +Port = 8080 +ReadTimeout = "5m" +WriteTimeout = "5m" +# As of aggkit v0.11.0-rc5, MaxRequestsPerIPAndSecond is unenforced in +# RESTConfig-backed sections (no middleware reads it) -- upstream's own +# default changed 10 -> 0 and docs/common_config.md's new RESTConfig section +# documents it as "unused; apply rate limiting at the infra layer" (see +# agglayer/aggkit#1783). Matching the upstream default +# here rather than pinning a value that implies in-process enforcement it +# doesn't have. Rate limiting for this service, if ever needed, belongs at +# haproxy or another fronting layer. +MaxRequestsPerIPAndSecond = 0 + +[Tracker] +# RetentionPeriod raised from the binary's 10m default so a slow L2->L1 demo +# certificate (agglayer settlement can take a while) stays queryable through +# /tracker/v1 instead of falling out of the registry mid-demo. +RetentionPeriod = "30m" +IdleTimeout = "30m" +RegisterResolveTimeout = "3s" +L1BlockFinality = "LatestBlock" +L2BlockFinality = "LatestBlock" +MaxTrackedBridges = 100000 +L1GlobalExitRootAddress = "0x1f7ad7caA53e35b4f0D138dC5CBF91aC108a2674" + +[Tracker.AgglayerClient] +Cached = true +[Tracker.AgglayerClient.ConfigurationCache] +TTL = "1s" +Capacity = 100 +SendCertificate = "forbidden" +GetCertificateHeader = "cached" +GetEpochConfiguration = "cached" +GetLatestPendingCertificateHeader = "cached" +GetNetworkInfo = "cached" + +[Tracker.AgglayerClient.GRPC] +URL = "http://agglayer:4443" +UseTLS = false +MinConnectTimeout = "5s" +RequestTimeout = "300s" + +[Tracker.AgglayerClient.GRPC.Retry] +InitialBackoff = "1s" +MaxBackoff = "10s" +BackoffMultiplier = 2.0 +MaxAttempts = 20 diff --git a/test/e2e/envs/anvil-2chains/config/agglayer/aggregator.keystore b/test/e2e/envs/anvil-2chains/config/agglayer/aggregator.keystore new file mode 100644 index 000000000..ae115f3ad --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/agglayer/aggregator.keystore @@ -0,0 +1,20 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "fcd94bfef1b7e17213e1f9d7de2c74d6" + }, + "ciphertext": "9760449127d568224487accab22842553f57dea74d442751db1225e19293e598", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 8192, + "p": 1, + "r": 8, + "salt": "a0695165cb05219ed8c3d3987c1a1a683a0d71920ad4b4b4fabe21b55c487f4d" + }, + "mac": "a5439eabc23a0df3972ba55dcfe9db9b79d80f686e8fd7ae3ac95dff7aefd4d7" + }, + "id": "9eafcbe9-478c-4ffe-abab-9f9c4fa6b316", + "version": 3 +} diff --git a/test/e2e/envs/anvil-2chains/config/agglayer/config.toml b/test/e2e/envs/anvil-2chains/config/agglayer/config.toml new file mode 100644 index 000000000..0d5837b78 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/config/agglayer/config.toml @@ -0,0 +1,141 @@ +debug-mode = true + + +# Only supported by fork 12+ +mock-verifier = true + + +[full-node-rpcs] +# OP Stack RPC (also used by the anvil L2, whose op_el_rpc_url is aliased to it) +1 = "http://l2-anvil-001:8545" +2 = "http://l2-anvil-002:8545" + +[proof-signers] +1 = "0x5b06837A43bdC3dD9F114558DAf4B26ed49842Ed" +2 = "0x5b06837A43bdC3dD9F114558DAf4B26ed49842Ed" +[prover.mock-prover] +proving-timeout = "5m" +proving-request-timeout = "300s" + +[rpc] +grpc-port = 4443 +readrpc-port = 4444 +admin-port = 4446 +host = "0.0.0.0" +request-timeout = 180 +# size is define in bytes e.g. 100 * 1024 * 1024 +# same for `max_response_body_size` +# default value is equal to 10MB +max-request-body-size = 104857600 + +[grpc] +# size is define in bytes e.g. 100 * 1024 * 1024 +# same for `max-encoding-message-size` +# default value is equal to 4MB +max-decoding-message-size = 104857600 + +# [outbound.rpc.settle] used to live here. It has had no effect on settlement +# since agglayer PR #1393 introduced the agglayer-settlement-service -- +# OutboundConfig is explicitly documented upstream as deprecated, and at +# v0.6.0-rc.7+ the binary itself warns on startup if this section is present +# (OutboundConfig::ignored_config_warning()). The real settlement-tx knobs +# live under [settlement.pessimistic-proof-tx-config] below. +[settlement.pessimistic-proof-tx-config] +# Number of L1 block confirmations required before a settlement tx's receipt +# is considered resolved. Upstream default is 12 (default_confirmations(), +# crates/agglayer-config/src/settlement_service.rs); this devnet had +# effectively been stuck at that default the whole time the dead +# [outbound.rpc.settle] confirmations=1 above was silently ignored. Lower +# values settle certificates faster at the cost of reorg safety -- fine on a +# throwaway anvil L1, not a production recommendation. +confirmations = 1 +# Finality level required for a settlement tx to be considered settled. +# Upstream enum is LatestBlock/SafeBlock/FinalizedBlock (SafeBlock is the +# upstream default); the latest->safe->finalized lag on an anvil L1 is +# l1_anvil_block_time * l1_anvil_slots_in_epoch seconds per step. The +# agglayer_settlement_policy input arg uses the lowercase +# latest/safe/finalized tokens; they're translated to the upstream wire +# enum names here since agglayer's SettlementPolicy has no +# #[serde(rename_all = "kebab-case")] (confirmed against +# crates/agglayer-config/tests/fixtures/settlement/*.toml at v0.6.0-rc.8, +# which all use the PascalCase variant names verbatim). +settlement-policy = "SafeBlock" +# retry-on-transient-failure and gas-limit-multiplier-factor are +# intentionally left unset here (upstream defaults apply): the old +# [outbound.rpc.settle] max-retries/retry-interval had no 1:1 mapping onto +# the new schema's two separate retry-policy tables, and settlement-timeout +# = 1200 has no equivalent anywhere in the new schema at all (grepped +# SettlementTransactionConfig/SettlementServiceConfig at v0.6.0-rc.8 -- no +# timeout field exists upstream; that intent has no home). +# +# A3 measurement (plans/snapshot-v2-aggkit-e2e/A3-evidence/): the first +# receipt check for a settlement tx fires immediately after broadcast (0 +# confirmations elapsed yet), returns NotIncludedYet, and upstream's default +# retry-on-not-included-on-l1.initial-interval (1m, unset here previously) +# gates the *next* attempt -- confirmed via the agglayer container log +# ("Transient error while executing retryable callback, error: +# NotIncludedYet, retry_attempt: 1, sleep_duration: 61.936s") on this exact +# env. Lowered here since the anvil L1 reaches 1 confirmation within ~1s. +[settlement.pessimistic-proof-tx-config.retry-on-not-included-on-l1] +initial-interval = "5s" + +[log] +# level = "info" +level = "debug" # we want debug visibility for now +outputs = ["stderr"] +format = "pretty" + +[auth.local] +private-keys = [ + # First entry = pp-settlement signer (certificate/PP settlement). + { path = "/etc/agglayer/aggregator.keystore", password = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" }, +] + +[l1] +chain-id = 271828 +node-url = "http://anvil-001:8545" +ws-node-url = "ws://anvil-001:8545" +rollup-manager-contract = "0x6c6c009cC348976dB4A908c92B24433d4F6edA43" +polygon-zkevm-global-exit-root-v2-contract = "0x1f7ad7caA53e35b4f0D138dC5CBF91aC108a2674" +rpc-timeout = 45 + +[l2] +rpc-timeout = 45 + +[telemetry] +prometheus-addr = "0.0.0.0:9092" + +# https://github.com/orgs/agglayer/discussions/213 + +[rate-limiting] +send-tx = "unlimited" +# [rate-limiting.send-tx] +# max-per-interval = 1 +# time-interval = "15m" + +[rate-limiting.network] + +# Bookkeeping-only from v0.6.0-rc.2 onward: per-epoch certificate rate +# limiting was deleted in commit 41d7a17e (PR #1615). epoch-duration still +# parses and drives epoch bookkeeping/storage indexing, but it moves neither +# settlement nor submission timing under trigger_cert_mode: ASAP (this +# package's default) -- do not treat it as a latency knob. +[epoch.block-clock] +epoch-duration = 15 +genesis-block = 60 + +[shutdown] +runtime-timeout = 5 + +[certificate-orchestrator] +input-backpressure-buffer-size = 1000 + +[certificate-orchestrator.prover.sp1-local] + +[storage] +db-path = "/etc/agglayer/storage" + +[storage.backup] +path = "/etc/agglayer/backups" +state-max-backup-count = 100 +pending-max-backup-count = 100 diff --git a/test/e2e/envs/anvil-2chains/docker-compose.yml b/test/e2e/envs/anvil-2chains/docker-compose.yml new file mode 100644 index 000000000..080aca38b --- /dev/null +++ b/test/e2e/envs/anvil-2chains/docker-compose.yml @@ -0,0 +1,201 @@ +# anvil-2chains E2E environment +# +# Source: a kurtosis-cdk anvil devnet snapshot (flavor "anvil-aggkit", kurtosis-cdk commit +# fc160450b55e64332436f11c091c61130c64030f) -- see README.md in this directory for full +# provenance (image tags/digests, params files, regenerate procedure). +# +# Topology: one anvil L1 (anvil-001) + two independent anvil L2 sovereign chains +# (l2-anvil-001 / l2-anvil-002), each with its own aggkit instance (merged +# aggsender+aggoracle+bridge+autoclaim, no separate "-bridge" sidecar), settling through a +# single agglayer in PessimisticProof mode, fronted by a shared aggkit-proxy. +# +# Images: anvil-001/l2-anvil-001/l2-anvil-002/agglayer are the digest-pinned kurtosis-cdk +# snapshot images (state + captured devnet config baked in, but agglayer's config is +# overridden below via bind mount so it can be tuned locally). aggkit-001/aggkit-002/ +# aggkit-proxy-001 run this repo's own locally built image (`make build-docker`), NOT the +# snapshot's baked aggkit image, so the binary under test is always the one checked out here. +# +# Healthcheck note (do not simplify away): agglayer's healthcheck below is a genuine +# TCP-connect probe against its own gRPC port (bash's /dev/tcp builtin -- no curl/wget/nc +# needed, confirmed present in the bare agglayer image). aggkit-00X and aggkit-proxy-001 gate +# on `condition: service_healthy` against agglayer using that probe, and themselves carry NO +# healthcheck (their image is genuinely distroless -- no shell at all). A merely +# "process started" agglayer dependency (`condition: service_started`) loses a real race +# against aggkit's own claim-syncer autostart and permanently wedges aggsender at +# "starting_claim_syncer_stage" ("cannot set next required block to 0, it must be >= the +# first block in DB") -- reproduced 3/3 in this plan's own K5/K5c evidence. Do not revert to +# aggkit's own op-pp-2chains precedent here (`test -f /proc/1/cmdline`): that check passes +# within milliseconds, before agglayer's gRPC listener actually binds, and does not close the +# race. + +services: + anvil-001: + image: ghcr.io/0xpolygon/kurtosis-cdk-snapshot-anvil-001@sha256:006932fc49ce501c8d6f8c3f4ac3b5873ec14b59b101b4f4e9db02b169e6c0c9 + hostname: anvil-001 + ports: + - "13545:8545" # L1 JSON-RPC + restart: unless-stopped + healthcheck: + test: ["CMD", "/bin/sh", "/snapshot/healthcheck.sh"] + interval: 3s + timeout: 10s + retries: 40 + start_period: 5s + + l2-anvil-001: + image: ghcr.io/0xpolygon/kurtosis-cdk-snapshot-l2-anvil-001@sha256:e9bbeb7f9a76a4ea725f194059c6b23d49c65e89a8a00241d6df3b687c8ccbb8 + hostname: l2-anvil-001 + ports: + - "14545:8545" # L2-001 (chain 20201) JSON-RPC + restart: unless-stopped + healthcheck: + test: ["CMD", "/bin/sh", "/snapshot/healthcheck.sh"] + interval: 3s + timeout: 10s + retries: 40 + start_period: 5s + + l2-anvil-002: + image: ghcr.io/0xpolygon/kurtosis-cdk-snapshot-l2-anvil-002@sha256:3555d50518f6f72d5811edd759293ba205ac192c04192695afc046c2cb595ef0 + hostname: l2-anvil-002 + ports: + - "15545:8545" # L2-002 (chain 20202) JSON-RPC + restart: unless-stopped + healthcheck: + test: ["CMD", "/bin/sh", "/snapshot/healthcheck.sh"] + interval: 3s + timeout: 10s + retries: 40 + start_period: 5s + + agglayer: + image: ghcr.io/0xpolygon/kurtosis-cdk-snapshot-agglayer@sha256:5a47d3778657ba618ff7dfc99dfd55a3863097d5fff4f960a0740d4d0ae80073 + hostname: agglayer + entrypoint: ["/usr/local/bin/agglayer"] + command: ["run", "--cfg", "/etc/agglayer/config.toml"] + environment: + - RUST_BACKTRACE=1 + volumes: + - ./config/agglayer/config.toml:/etc/agglayer/config.toml:ro + - ./config/agglayer/aggregator.keystore:/etc/agglayer/aggregator.keystore:ro + ports: + - "13443:4443" # gRPC + - "13444:4444" # read RPC + - "13446:4446" # admin API + - "13092:9092" # prometheus + depends_on: + anvil-001: + condition: service_healthy + l2-anvil-001: + condition: service_healthy + l2-anvil-002: + condition: service_healthy + restart: unless-stopped + healthcheck: + # Genuine TCP-connect probe against agglayer's own gRPC port -- see the file header + # comment for why this matters (aggkit-00X's aggsender races agglayer's async + # gRPC-listener bind against its own local claim-syncer autostart on startup). + test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/4443"] + interval: 2s + timeout: 3s + retries: 30 + start_period: 10s + + aggkit-001: + image: aggkit:local + hostname: aggkit-001 + # Matches op-pp/docker-compose.yml's aggkit-001: since /tmp is bind-mounted to the host + # below (for GetAggsenderDBPath()/GetAggkitDataDir()), run as the host UID/GID (injected by + # newDockerComposeCmd) so files the container writes to /tmp stay host-writable/removable + # instead of landing owned by the container's own root (which cleanAggkitDataDir then can't + # remove on the next run under non-rootless Docker). + user: "${UID:-1000}:${GID:-1000}" + entrypoint: ["/usr/local/bin/aggkit"] + command: + - "run" + - "--cfg=/etc/aggkit/config.toml" + - "--components=aggsender,aggoracle,bridge,autoclaim" + volumes: + - ./config/001/aggkit-config.toml:/etc/aggkit/config.toml:ro + - ./config/001/sequencer.keystore:/etc/aggkit/sequencer.keystore:ro + - ./config/001/aggoracle.keystore:/etc/aggkit/aggoracle.keystore:ro + - ./config/001/sovereignadmin.keystore:/etc/aggkit/sovereignadmin.keystore:ro + - ./aggkit-001-data:/tmp + ports: + - "14576:5576" # JSON-RPC (debug) + - "14577:5577" # bridge REST API + - "14579:5579" # admin REST API + depends_on: + anvil-001: + condition: service_healthy + l2-anvil-001: + condition: service_healthy + agglayer: + condition: service_healthy + restart: unless-stopped + environment: + - RUST_BACKTRACE=1 + # aggkit-001 carries NO healthcheck of its own -- its image is genuinely distroless + # (no shell at all). Its own depends_on above on agglayer is `service_healthy` (see the + # file header comment); dependents of aggkit-001 (aggkit-proxy-001, below) still use + # `condition: service_started`, since aggkit-001 has no healthcheck to gate on. + + aggkit-002: + image: aggkit:local + hostname: aggkit-002 + entrypoint: ["/usr/local/bin/aggkit"] + command: + - "run" + - "--cfg=/etc/aggkit/config.toml" + - "--components=aggsender,aggoracle,bridge,autoclaim" + volumes: + - ./config/002/aggkit-config.toml:/etc/aggkit/config.toml:ro + - ./config/002/sequencer.keystore:/etc/aggkit/sequencer.keystore:ro + - ./config/002/aggoracle.keystore:/etc/aggkit/aggoracle.keystore:ro + - ./config/002/sovereignadmin.keystore:/etc/aggkit/sovereignadmin.keystore:ro + ports: + - "15576:5576" # JSON-RPC (debug) + - "15577:5577" # bridge REST API + - "15579:5579" # admin REST API + depends_on: + anvil-001: + condition: service_healthy + l2-anvil-002: + condition: service_healthy + agglayer: + condition: service_healthy + restart: unless-stopped + environment: + - RUST_BACKTRACE=1 + # Same "no healthcheck, distroless image" note as aggkit-001 above. + + aggkit-proxy-001: + image: aggkit:local + hostname: aggkit-proxy-001 + entrypoint: ["/usr/local/bin/aggkit-proxy"] + command: + - "run" + - "--cfg=/etc/aggkit-proxy/config.toml" + - "--components=proxy,tracker" + volumes: + - ./config/aggkit-proxy/aggkit-proxy.toml:/etc/aggkit-proxy/config.toml:ro + ports: + - "15601:8080" # bridge + tracker REST + depends_on: + agglayer: + condition: service_healthy + aggkit-001: + condition: service_started + aggkit-002: + condition: service_started + restart: unless-stopped + environment: + - RUST_BACKTRACE=1 + # Same "no healthcheck, distroless image" note as aggkit-00X above. Its own depends_on + # on agglayer is `service_healthy`; its depends_on on aggkit-00X stays `service_started` + # since aggkit-00X has no healthcheck of its own. + +# Anvil family (anvil-001/l2-anvil-001/l2-anvil-002) keeps its baked state -- no bind mounts, +# never overridden: swapping config there would lose the captured devnet state. agglayer/ +# aggkit-00X/aggkit-proxy-001 all bind-mount their config from ./config/ (read-only) so A3 can +# tune them without rebuilding an image. diff --git a/test/e2e/envs/anvil-2chains/summary.json b/test/e2e/envs/anvil-2chains/summary.json new file mode 100644 index 000000000..c69e015a7 --- /dev/null +++ b/test/e2e/envs/anvil-2chains/summary.json @@ -0,0 +1,269 @@ +{ + "snapshot_name": "anvil-2chains (kurtosis-cdk fc160450, K8 publish run 31787941750)", + "enclave": "cdk", + "created_at": "2026-08-14T09:33:46Z", + "networks": { + "l1": { + "chain_id": "271828", + "contracts": { + "rollup_manager": "0x6c6c009cC348976dB4A908c92B24433d4F6edA43", + "global_exit_root_v2": "0x1f7ad7caA53e35b4f0D138dC5CBF91aC108a2674", + "bridge": "0xC8cbEBf950B9Df44d987c8619f092beA980fF038", + "pol_token": "0xEdE9cf798E0fE25D35469493f43E88FeA4a5da0E" + }, + "services": { + "geth": { + "http_rpc": { + "internal": "http://anvil-001:8545", + "external": "http://localhost:13545" + } + } + }, + "accounts": [ + { + "address": "0x8943545177806ED17B9F23F0a21ee5948eCaa776", + "private_key": "0xbcdf20249abf0ed6d944c0288fad489e33f66b3960d9e6229c1cd214ed3bbe31", + "description": "L1 pre-funded account" + }, + { + "address": "0xE25583099BA105D9ec0A67f5Ae86D90e50036425", + "private_key": "0x39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d", + "description": "L1 pre-funded account" + }, + { + "address": "0x614561D2d143621E126e87831AEF287678B442b8", + "private_key": "0x53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710", + "description": "L1 pre-funded account" + }, + { + "address": "0xf93Ee4Cf8c6c40b329b0c0626F28333c132CF241", + "private_key": "0xab63b23eb7941c1251757e24b3d2350d2bc05c3c388d06f8fe6feafefb1e8c70", + "description": "L1 pre-funded account" + }, + { + "address": "0x802dCbE1B1A97554B4F50DB5119E37E8e7336417", + "private_key": "0x5d2344259f42259f82d2c140aa66102ba89b57b4883ee441a8b312622bd42491", + "description": "L1 pre-funded account" + }, + { + "address": "0xAe95d8DA9244C37CaC0a3e16BA966a8e852Bb6D6", + "private_key": "0x27515f805127bebad2fb9b183508bdacb8c763da16f54e0678b16e8f28ef3fff", + "description": "L1 pre-funded account" + }, + { + "address": "0x2c57d1CFC6d5f8E4182a56b4cf75421472eBAEa4", + "private_key": "0x7ff1a4c1d57e5e784d327c4c7651e952350bc271f156afb3d00d20f5ef924856", + "description": "L1 pre-funded account" + }, + { + "address": "0x741bFE4802cE1C4b5b00F9Df2F5f179A1C89171A", + "private_key": "0x3a91003acaf4c21b3953d94fa4a6db694fa69e5242b2e37be05dd82761058899", + "description": "L1 pre-funded account" + }, + { + "address": "0xc3913d4D8bAb4914328651C2EAE817C8b78E1f4c", + "private_key": "0xbb1d0f125b4fb2bb173c318cdead45468474ca71474e2247776b2b4c0fa2d3f5", + "description": "L1 pre-funded account" + }, + { + "address": "0x65D08a056c17Ae13370565B04cF77D2AfA1cB9FA", + "private_key": "0x850643a0224065ecce3882673c21f56bcf6eef86274cc21cadff15930b59fc8c", + "description": "L1 pre-funded account" + } + ] + }, + "agglayer": { + "services": { + "grpc_rpc": { + "internal": "http://agglayer:4443", + "external": "http://localhost:13443" + }, + "read_rpc": { + "internal": "http://agglayer:4444", + "external": "http://localhost:13444" + }, + "admin_api": { + "internal": "http://agglayer:4446", + "external": "http://localhost:13446" + }, + "metrics": { + "internal": "http://agglayer:9092/metrics", + "external": "http://localhost:13092/metrics" + } + } + }, + "l2_networks": { + "001": { + "chain_id": "20201", + "contracts": { + "l1_bridge": "0xC8cbEBf950B9Df44d987c8619f092beA980fF038", + "l2_bridge": "0xC8cbEBf950B9Df44d987c8619f092beA980fF038", + "rollup_manager": "0x6c6c009cC348976dB4A908c92B24433d4F6edA43", + "global_exit_root": "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa", + "sovereign_rollup_l1": "0x414e9E227e4b589aF92200508aF5399576530E4e" + }, + "services": { + "op-geth": { + "http_rpc": { + "internal": "http://l2-anvil-001:8545", + "external": "http://localhost:14545" + } + }, + "aggkit": { + "rpc": { + "internal": "http://aggkit-001:5576", + "external": "http://localhost:14576" + }, + "rest_api": { + "internal": "http://aggkit-001:5577", + "external": "http://localhost:14577" + } + } + }, + "accounts": [ + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + "private_key": "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", + "private_key": "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "private_key": "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "private_key": "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "private_key": "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "private_key": "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "description": "L2 network 001 pre-funded account" + }, + { + "address": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + "private_key": "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + "description": "L2 network 001 pre-funded account" + } + ] + }, + "002": { + "chain_id": "20202", + "contracts": { + "l1_bridge": "0xC8cbEBf950B9Df44d987c8619f092beA980fF038", + "l2_bridge": "0xC8cbEBf950B9Df44d987c8619f092beA980fF038", + "rollup_manager": "0x6c6c009cC348976dB4A908c92B24433d4F6edA43", + "global_exit_root": "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa", + "sovereign_rollup_l1": "0x5D1A491A416feEbf8C123A558ec28A239960bd0E" + }, + "services": { + "op-geth": { + "http_rpc": { + "internal": "http://l2-anvil-002:8545", + "external": "http://localhost:15545" + } + }, + "aggkit": { + "rpc": { + "internal": "http://aggkit-002:5576", + "external": "http://localhost:15576" + }, + "rest_api": { + "internal": "http://aggkit-002:5577", + "external": "http://localhost:15577" + } + } + }, + "accounts": [ + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + "private_key": "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", + "private_key": "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "private_key": "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "private_key": "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "private_key": "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "private_key": "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "description": "L2 network 002 pre-funded account" + }, + { + "address": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + "private_key": "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + "description": "L2 network 002 pre-funded account" + } + ] + } + } + }, + "test_accounts": { + "l1_mnemonic": "giant issue aisle success illegal bike spike question tent bar rely arctic volcano long crawl hungry vocal artwork sniff fantasy very lucky have athlete", + "l2_mnemonic": "test test test test test test test test test test test junk", + "note": "Pre-funded test accounts are derived from these mnemonics -- byte-identical to op-pp-2chains's own mnemonics, so every contract address above matches op-pp-2chains's address-for-address. Use with cast: cast wallet address --mnemonic \"\" --mnemonic-index <0-N>" + }, + "notes": { + "json_labels": "The 'geth' (L1) and 'op-geth' (L2) service labels above are aggkit's schema field names, not the running service technology: every execution client in this env is anvil (Foundry v1.5.1), not go-ethereum. aggkit's summaryJSON struct offers no third label, so these names are reused verbatim -- see README.md.", + "accounts": "Only mnemonic-derived pre-funded accounts with a private_key are included (10 per network, funded_on-filtered from the kurtosis-cdk bundle's accounts.funded list). Precompile/predeploy addresses are excluded.", + "services": "Internal URLs are for use within the Docker network (docker-compose service DNS names). External URLs are for access from the host machine.", + "provenance": "Full provenance (kurtosis-cdk commit, params files, image tags/digests, regenerate procedure) is recorded in this directory's README.md, not here -- this file only carries what test/e2e/envs/loader.go and checks.go actually read." + } +} diff --git a/test/e2e/envs/checks.go b/test/e2e/envs/checks.go index d76f124e0..11246f157 100644 --- a/test/e2e/envs/checks.go +++ b/test/e2e/envs/checks.go @@ -66,9 +66,11 @@ func (e *Env) checkConfiguration() error { return fmt.Errorf("L1 Transactor is nil") } - // Expected L2A chain ID depends on the env: op-pp uses 2151908, op-pp-2chains uses 20201. + // Expected L2A chain ID depends on the env: op-pp uses 2151908, op-pp-2chains and + // anvil-2chains both use 20201. Not derived from summary.json: this check exists to catch a + // stale/wrong summary, so comparing a parsed value against itself would be circular. wantL2AChainID := "2151908" - if e.envName == EnvOpPP2Chains { + if e.envName == EnvOpPP2Chains || e.envName == EnvAnvil2Chains { wantL2AChainID = "20201" } if err := checkL2Configured(e.L2, wantL2AChainID, "L2"); err != nil { diff --git a/test/e2e/envs/loader.go b/test/e2e/envs/loader.go index 0dfaecf5d..12c413a3c 100644 --- a/test/e2e/envs/loader.go +++ b/test/e2e/envs/loader.go @@ -37,6 +37,10 @@ const ( // EnvOpPP2Chains is a testing env that has two OP-PP L2 networks deployed (L2A + L2B) EnvOpPP2Chains ENVName = "op-pp-2chains" + // EnvAnvil2Chains is a testing env that has two anvil-backed L2 networks deployed (L2A + L2B), + // settling against an anvil L1, sourced from a kurtosis-cdk anvil snapshot bundle. + EnvAnvil2Chains ENVName = "anvil-2chains" + // l2NetworkKeyA is the summary.json key of the primary L2 network (L2A). l2NetworkKeyA = "001" // l2NetworkKeyB is the summary.json key of the secondary L2 network (L2B), present @@ -310,9 +314,11 @@ func LoadEnv(ctx context.Context, envName ENVName) (*Env, error) { return nil, fmt.Errorf("load L2 network %s: %w", l2NetworkKeyA, err) } - // Load secondary L2 network (L2B, key "002") only for multi-chain envs. + // Load secondary L2 network (L2B, key "002") only for multi-chain envs, detected by the + // presence of the "002" key in summary.json rather than by env name. This generalizes to any + // multi-chain env (present and future) without adding a name to a list. var l2B *L2Config - if envName == EnvOpPP2Chains { + if _, ok := summary.Networks.L2Networks[l2NetworkKeyB]; ok { l2B, err = loadL2Config(ctx, summary, l2NetworkKeyB) if err != nil { return nil, fmt.Errorf("load L2 network %s: %w", l2NetworkKeyB, err) @@ -693,6 +699,27 @@ func (e *Env) DockerComposeLogs(ctx context.Context, args ...string) ([]byte, er return out, nil } +// ComposeServices returns every service name defined in this environment's docker-compose.yml, by +// shelling out to "docker compose config --services". This is used instead of a hardcoded per-env +// service list (which cannot stay in sync across envs, and summary.json's schema has no key for +// services like beacon/validator/op-node) so log collection covers every service in any env, +// present or future, with zero per-env code. +func (e *Env) ComposeServices(ctx context.Context) ([]string, error) { + cmd := newDockerComposeCmd(ctx, e.EnvDir, "config", "--services") + out, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("docker compose config --services: %w\nOutput:\n%s", err, string(out)) + } + var services []string + for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { + line = strings.TrimSpace(line) + if line != "" { + services = append(services, line) + } + } + return services, nil +} + // cleanAggkitDataDir removes the aggkit data directory and recreates it with /tmp-like // permissions so bind-mounts remain writable even under rootless Docker, where the // mount may appear as root:root inside the container. diff --git a/test/e2e/forcegerupdate_test.go b/test/e2e/forcegerupdate_test.go index 023b45d35..e0a384bd6 100644 --- a/test/e2e/forcegerupdate_test.go +++ b/test/e2e/forcegerupdate_test.go @@ -1,5 +1,5 @@ // Package e2e — TestForceGERUpdateE2E exercises the force_ger_update CLI tool (tools/force_ger_update) -// as a real subprocess against the docker-compose op-pp environment. +// as a real subprocess against the docker-compose Anvil environment. // // Run with (docker-compose env brought up automatically by TestMain/envs.LoadEnv): // @@ -305,7 +305,7 @@ func buildForceGERUpdateBinary(ctx context.Context, t *testing.T) string { return binaryPath } -// TestForceGERUpdateE2E runs the force_ger_update tool as a real subprocess against the op-pp +// TestForceGERUpdateE2E runs the force_ger_update tool as a real subprocess against the Anvil // docker-compose env and proves it forces an on-chain L1 GER update when none happens organically // within MaxTimeWithoutGERUpdate. // @@ -313,7 +313,7 @@ func buildForceGERUpdateBinary(ctx context.Context, t *testing.T) string { // // go test -v -timeout 30m -run TestForceGERUpdateE2E ./test/e2e/... func TestForceGERUpdateE2E(t *testing.T) { - // Like the remove_ger e2e tests, forcing a GER update perturbs the shared op-pp env's post-test + // Like the remove_ger e2e tests, forcing a GER update perturbs the shared Anvil env's post-test // L1<->L2 bridge health check that TestMain runs (see testmain_test.go), which can then time out // even though this test's own assertions pass. Rather than skipping unconditionally, this test // only runs when explicitly opted into via RUN_FORCE_GER_UPDATE_E2E=true — the dedicated CI job diff --git a/test/e2e/proxy_tracker_test.go b/test/e2e/proxy_tracker_test.go index 0ba8f57c8..4d11ffdb0 100644 --- a/test/e2e/proxy_tracker_test.go +++ b/test/e2e/proxy_tracker_test.go @@ -20,10 +20,8 @@ import ( "github.com/stretchr/testify/require" ) -// proxyTrackerBaseURL is the external (host) URL of the aggkit-proxy REST API, which serves the -// bridge tracker. It only exists in the 2-chain env (test/e2e/envs/op-pp-2chains/docker-compose.yml, -// service aggkit-proxy-001, port 12601 -> 8080); the single-chain env (op-pp) has no proxy at all. -const proxyTrackerBaseURL = "http://127.0.0.1:12601" +// proxyTrackerBaseURL is the external (host) URL of the Anvil env's aggkit-proxy REST API. +const proxyTrackerBaseURL = "http://127.0.0.1:15601" // trackerBridgeEventData mirrors api.BridgeEventData (bridgetracker/api/bridge_status.go): the // facts taken directly from the on-chain BridgeEvent log. @@ -190,15 +188,14 @@ func claimL1ToL2(ctx context.Context, env *envs.Env, l2Opts *bind.TransactOpts, // (the tracker only reports status, it never builds/sends claim transactions itself) and asserts // the tracker follows it through to its terminal Claimed/finished state. // -// It requires the multi-chain env (EnvOpPP2Chains): aggkit-proxy is only wired there (see -// docker-compose.yml). When run against the single-chain env (testEnv.L2B == nil) it is skipped. +// It requires a multi-chain env with aggkit-proxy configured. func TestBridgeTrackerL1ToL2(t *testing.T) { if testing.Short() { t.Skip("Skipping E2E test in short mode") } require.NotNil(t, testEnv, "testEnv must be set by TestMain") if testEnv.L2B == nil { - t.Skip("bridge tracker test requires EnvOpPP2Chains (aggkit-proxy is only wired there)") + t.Skip("bridge tracker test requires a multi-chain env (L2B must be non-nil)") } ctx, cancel := context.WithTimeout(context.Background(), 12*time.Minute) diff --git a/test/e2e/removeger_test.go b/test/e2e/removeger_test.go index 2abce7a8a..720332620 100644 --- a/test/e2e/removeger_test.go +++ b/test/e2e/removeger_test.go @@ -35,16 +35,14 @@ import ( ) const ( - opPPEnvName = "op-pp" keystorePassword = "pSnv6Dh5s9ahuzGzH9RoCDrKAMddaX3m" backoffInitial = 500 * time.Millisecond backoffMax = 10 * time.Second // l2GERStallObserveWindow bounds how long assertL2GERSyncStalledAt watches /sync-status to - // confirm l2gersync has stopped advancing while the invalid GER is present. The op-pp env's L2 - // (op-stack) produces a block every second (test/e2e/envs/op-pp/config/001/rollup.json - // "block_time": 1), so this window comfortably covers many L2 blocks, giving a clear signal that - // the chain moves while l2gersync does not. + // confirm l2gersync has stopped advancing while the invalid GER is present. The Anvil L2 + // produces blocks frequently, so this window comfortably covers many L2 blocks and gives a clear + // signal that the chain moves while l2gersync does not. l2GERStallObserveWindow = 20 * time.Second // l2GERCatchUpTimeout bounds how long waitForL2GERSyncCaughtUp waits for l2gersync to resume and // process past the removal block after ExecuteRecovery removes the invalid GER on-chain. @@ -670,6 +668,7 @@ type summaryForToolConfig struct { Services struct { Geth struct { HTTPRpc struct { + Internal string `json:"internal"` External string `json:"external"` } `json:"http_rpc"` } `json:"geth"` @@ -688,6 +687,7 @@ type summaryForToolConfig struct { } `json:"aggkit"` OpGeth struct { HTTPRpc struct { + Internal string `json:"internal"` External string `json:"external"` } `json:"http_rpc"` } `json:"op-geth"` @@ -714,15 +714,17 @@ func prepareToolConfig(t *testing.T, configDir string) string { bridgeServiceURL := l2Network.Services.Aggkit.BridgeService.External l1URL := summary.Networks.L1.Services.Geth.HTTPRpc.External l2URL := l2Network.Services.OpGeth.HTTPRpc.External + require.NotEmpty(t, summary.Networks.L1.Services.Geth.HTTPRpc.Internal, "L1 internal RPC URL must be present") + require.NotEmpty(t, l2Network.Services.OpGeth.HTTPRpc.Internal, "L2 internal RPC URL must be present") sovereignAdminKeyPath := filepath.Join(testEnv.EnvDir, "config", "001", "sovereignadmin.keystore") originalCfg := filepath.Join(testEnv.EnvDir, "config", "001", "aggkit-config.toml") content, err := os.ReadFile(originalCfg) require.NoError(t, err) - // Patch internal URLs so the tool (running on host) can reach L1/L2. - content = []byte(strings.ReplaceAll(string(content), "http://geth:8545", l1URL)) - content = []byte(strings.ReplaceAll(string(content), "http://op-geth-001:8545", l2URL)) + // Patch the environment's internal URLs so the tool (running on the host) can reach L1/L2. + content = []byte(strings.ReplaceAll(string(content), summary.Networks.L1.Services.Geth.HTTPRpc.Internal, l1URL)) + content = []byte(strings.ReplaceAll(string(content), l2Network.Services.OpGeth.HTTPRpc.Internal, l2URL)) appendSection := fmt.Sprintf(` @@ -871,7 +873,7 @@ func testRemoveGER_NoProblematicClaims(t *testing.T) { // removalBlock is the exact block of the removeGlobalExitRoots tx (from ExecuteRecovery's receipt). // l2gersync only needs to process this fixed past block to observe the removal and unstick; targeting - // the live L2 head instead would chase a moving target several blocks ahead (op-pp mines ~1 block/s). + // the live L2 head instead would chase a moving target several blocks ahead. removalBlock := recovery.RemovalBlock require.NotZero(t, removalBlock, "recovery must report the removeGlobalExitRoots block") waitForL2GERSyncCaughtUp(ctx, t, env, removalBlock, l2GERCatchUpTimeout) @@ -921,7 +923,7 @@ func testRemoveGER_CategoryA(t *testing.T) { // Crafted like agglayer/e2e latest-n-injected-ger.bats: fixed GER (batsGER1), exit roots that hash to it, // hardcoded local merkle proof (batsLocalExitRootProof), and all-zero rollup proof. The claim is sent with // AggOracle key to match the bats test. If ClaimAsset reverts, the bats proof may be for a different - // CDK/bridge deployment (e.g. Kurtosis env) and may need to be regenerated for this op-pp snapshot. + // CDK/bridge deployment and may need to be regenerated for a newer snapshot. injectedGER := batsGER1 require.Equal(t, injectedGER, l1infotreesync.CalculateGER(mainnetExitRootBats, rollupExitRootBats), "bats GER must equal keccak256(mainnetExitRootBats, rollupExitRootBats)") @@ -976,7 +978,7 @@ func testRemoveGER_CategoryA(t *testing.T) { // removalBlock is the exact block of the removeGlobalExitRoots tx (from ExecuteRecovery's receipt). // l2gersync only needs to process this fixed past block to observe the removal and unstick; targeting - // the live L2 head instead would chase a moving target several blocks ahead (op-pp mines ~1 block/s). + // the live L2 head instead would chase a moving target several blocks ahead. removalBlock := recovery.RemovalBlock require.NotZero(t, removalBlock, "recovery must report the removeGlobalExitRoots block") waitForL2GERSyncCaughtUp(ctx, t, env, removalBlock, l2GERCatchUpTimeout) @@ -1086,7 +1088,7 @@ func testRemoveGER_CategoryB1(t *testing.T) { // removalBlock is the exact block of the removeGlobalExitRoots tx (from ExecuteRecovery's receipt). // l2gersync only needs to process this fixed past block to observe the removal and unstick; targeting - // the live L2 head instead would chase a moving target several blocks ahead (op-pp mines ~1 block/s). + // the live L2 head instead would chase a moving target several blocks ahead. removalBlock := recovery.RemovalBlock require.NotZero(t, removalBlock, "recovery must report the removeGlobalExitRoots block") waitForL2GERSyncCaughtUp(ctx, t, env, removalBlock, l2GERCatchUpTimeout) @@ -1238,7 +1240,7 @@ func testRemoveGER_CategoryB2(t *testing.T) { // removalBlock is the exact block of the removeGlobalExitRoots tx (from ExecuteRecovery's receipt). // l2gersync only needs to process this fixed past block to observe the removal and unstick; targeting - // the live L2 head instead would chase a moving target several blocks ahead (op-pp mines ~1 block/s). + // the live L2 head instead would chase a moving target several blocks ahead. removalBlock := recovery.RemovalBlock require.NotZero(t, removalBlock, "recovery must report the removeGlobalExitRoots block") waitForL2GERSyncCaughtUp(ctx, t, env, removalBlock, l2GERCatchUpTimeout) diff --git a/test/e2e/testmain_test.go b/test/e2e/testmain_test.go index b712f7aab..639fa881d 100644 --- a/test/e2e/testmain_test.go +++ b/test/e2e/testmain_test.go @@ -15,23 +15,25 @@ import ( var testEnv *envs.Env -// containerLogServices lists the docker compose services whose logs are dumped to -// test/e2e/.log when a test run fails, so the CI artifact-upload step (which globs -// test/e2e/*.log) actually captures something to debug the failure with. -var containerLogServices = []string{ - "geth", "beacon", "validator", "op-geth-001", "op-node-001", "aggkit-001", "agglayer", -} - -// dumpContainerLogs writes "docker compose logs" output for each service in containerLogServices to -// test/e2e/.log, relative to the test binary's working directory (test/e2e when run via -// `go test ./test/e2e/...`, matching the CI artifact glob). Services absent from the loaded env -// (e.g. an env without op-node-001) simply error and are skipped; failures here are logged, not -// fatal, since this only runs to aid debugging an already-failed run. +// dumpContainerLogs writes "docker compose logs" output for every service in the loaded env's +// docker-compose.yml (discovered via Env.ComposeServices, i.e. "docker compose config --services") +// to test/e2e/.log, relative to the test binary's working directory (test/e2e when run +// via `go test ./test/e2e/...`, matching the CI artifact glob). This covers every service in any +// env, present or future, with zero per-env code -- summary.json's schema has no key for services +// like beacon/validator/op-node, so a hardcoded list (or a summary.json-derived one) would always +// under-cover. Failures here are logged, not fatal, since this only runs to aid debugging an +// already-failed run. func dumpContainerLogs(env *envs.Env) { ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() - for _, service := range containerLogServices { + services, err := env.ComposeServices(ctx) + if err != nil { + log.Infof("[TEARDOWN] failed to list compose services: %v", err) + return + } + + for _, service := range services { out, err := env.DockerComposeLogs(ctx, "--no-log-prefix", service) if err != nil { log.Infof("[TEARDOWN] failed to fetch logs for service %q: %v", service, err) @@ -61,11 +63,10 @@ func TestMain(m *testing.M) { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute) - // Select which env to load via AGGKIT_E2E_ENV (used by CI to run the 2-chain matrix); - // defaults to the single-chain op-pp env. + // Select which env to load via AGGKIT_E2E_ENV; default to the two-chain Anvil env. envName := envs.ENVName(os.Getenv("AGGKIT_E2E_ENV")) if envName == "" { - envName = envs.EnvOpPP + envName = envs.EnvAnvil2Chains } env, err := envs.LoadEnv(ctx, envName)