[
  {
    "path": ".dockerignore",
    "content": "geth-data/\nreth-data/\nnethermind-data/\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Tag Docker image\n\non:\n  push:\n    branches:\n      - \"main\"\n    tags:\n      - \"v*\"\n  workflow_dispatch: {}\n\nenv:\n  REGISTRY: ghcr.io\n  NAMESPACE: ghcr.io/base\n  GETH_DEPRECATED_IMAGE_NAME: node\n  GETH_IMAGE_NAME: node-geth\n  RETH_IMAGE_NAME: node-reth\n  NETHERMIND_IMAGE_NAME: node-nethermind\n\npermissions:\n  contents: read\n  packages: write\n\njobs:\n  geth:\n    strategy:\n      matrix:\n        settings:\n          - arch: linux/amd64\n            runs-on: ubuntu-24.04\n          - arch: linux/arm64\n            runs-on: ubuntu-24.04-arm\n    runs-on: ${{ matrix.settings.runs-on }}\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n\n      - name: Log into the Container registry\n        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata for the Docker image\n        id: meta\n        uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4.6.0\n        with:\n          images: |\n            ${{ env.NAMESPACE }}/${{ env.GETH_DEPRECATED_IMAGE_NAME }}\n            ${{ env.NAMESPACE }}/${{ env.GETH_IMAGE_NAME }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Build and push the Docker image\n        id: build\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0\n        with:\n          context: .\n          file: geth/Dockerfile\n          tags: ${{ env.NAMESPACE }}/${{ env.GETH_DEPRECATED_IMAGE_NAME }},${{ env.NAMESPACE }}/${{ env.GETH_IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n          platforms: ${{ matrix.settings.arch }}\n          outputs: type=image,push-by-digest=true,name-canonical=true,push=true\n\n      - name: Export digest\n        run: |\n          mkdir -p ${{ runner.temp }}/digests\n          digest=\"${{ steps.build.outputs.digest }}\"\n          touch \"${{ runner.temp }}/digests/${digest#sha256:}\"\n\n      - name: Prepare\n        run: |\n          platform=${{ matrix.settings.arch }}\n          echo \"PLATFORM_PAIR=${platform//\\//-}\" >> $GITHUB_ENV\n\n      - name: Upload digest\n        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n        with:\n          name: digests-geth-${{ env.PLATFORM_PAIR }}\n          path: ${{ runner.temp }}/digests/*\n          if-no-files-found: error\n          retention-days: 1\n  reth:\n    strategy:\n      matrix:\n        settings:\n          - arch: linux/amd64\n            runs-on: ubuntu-24.04\n            features: jemalloc,asm-keccak,optimism\n          - arch: linux/arm64\n            runs-on: ubuntu-24.04-arm\n            features: jemalloc,optimism\n    runs-on: ${{ matrix.settings.runs-on }}\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout\n        uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0\n\n      - name: Log into the Container registry\n        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata for the Docker image\n        id: meta\n        uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4.6.0\n        with:\n          images: |\n            ${{ env.NAMESPACE }}/${{ env.RETH_IMAGE_NAME }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Build and push the Docker image\n        id: build\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0\n        with:\n          context: .\n          file: reth/Dockerfile\n          tags: ${{ env.NAMESPACE }}/${{ env.RETH_IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n          build-args: |\n            FEATURES=${{ matrix.settings.features }}\n          platforms: ${{ matrix.settings.arch }}\n          outputs: type=image,push-by-digest=true,name-canonical=true,push=true\n\n      - name: Export digest\n        run: |\n          mkdir -p ${{ runner.temp }}/digests\n          digest=\"${{ steps.build.outputs.digest }}\"\n          touch \"${{ runner.temp }}/digests/${digest#sha256:}\"\n\n      - name: Prepare\n        run: |\n          platform=${{ matrix.settings.arch }}\n          echo \"PLATFORM_PAIR=${platform//\\//-}\" >> $GITHUB_ENV\n\n      - name: Upload digest\n        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n        with:\n          name: digests-reth-${{ env.PLATFORM_PAIR }}\n          path: ${{ runner.temp }}/digests/*\n          if-no-files-found: error\n          retention-days: 1\n\n  nethermind:\n    strategy:\n      matrix:\n        settings:\n          - arch: linux/amd64\n            runs-on: ubuntu-24.04\n          - arch: linux/arm64\n            runs-on: ubuntu-24.04-arm\n    runs-on: ${{ matrix.settings.runs-on }}\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout\n        uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0\n\n      - name: Log into the Container registry\n        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Extract metadata for the Docker image\n        id: meta\n        uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4.6.0\n        with:\n          images: |\n            ${{ env.NAMESPACE }}/${{ env.NETHERMIND_IMAGE_NAME }}\n\n      - name: Build and push the Docker image\n        id: build\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0\n        with:\n          context: .\n          file: nethermind/Dockerfile\n          tags: ${{ env.NAMESPACE }}/${{ env.NETHERMIND_IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n          platforms: ${{ matrix.settings.arch }}\n          outputs: type=image,push-by-digest=true,name-canonical=true,push=true\n\n      - name: Export digest\n        run: |\n          mkdir -p ${{ runner.temp }}/digests\n          digest=\"${{ steps.build.outputs.digest }}\"\n          touch \"${{ runner.temp }}/digests/${digest#sha256:}\"\n\n      - name: Prepare\n        run: |\n          platform=${{ matrix.settings.arch }}\n          echo \"PLATFORM_PAIR=${platform//\\//-}\" >> $GITHUB_ENV\n\n      - name: Upload digest\n        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n        with:\n          name: digests-nethermind-${{ env.PLATFORM_PAIR }}\n          path: ${{ runner.temp }}/digests/*\n          if-no-files-found: error\n          retention-days: 1\n\n  merge-geth:\n    runs-on: ubuntu-latest\n    needs:\n      - geth\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Download digests\n        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0\n        with:\n          path: ${{ runner.temp }}/digests\n          pattern: digests-geth-*\n          merge-multiple: true\n\n      - name: Log into the Container registry\n        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Extract metadata for the Docker image\n        id: meta\n        uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0\n        with:\n          images: |\n            ${{ env.NAMESPACE }}/${{ env.GETH_DEPRECATED_IMAGE_NAME }}\n            ${{ env.NAMESPACE }}/${{ env.GETH_IMAGE_NAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=tag\n            type=sha,format=long\n\n      - name: Create manifest list and push\n        working-directory: ${{ runner.temp }}/digests\n        run: |\n          docker buildx imagetools create $(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\") \\\n            $(printf '${{ env.NAMESPACE }}/${{ env.GETH_DEPRECATED_IMAGE_NAME }}@sha256:%s ' *)\n          docker buildx imagetools create $(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\") \\\n            $(printf '${{ env.NAMESPACE }}/${{ env.GETH_IMAGE_NAME }}@sha256:%s ' *)\n\n      - name: Inspect image\n        run: |\n          docker buildx imagetools inspect ${{ env.NAMESPACE }}/${{ env.GETH_DEPRECATED_IMAGE_NAME }}:${{ steps.meta.outputs.version }}\n          docker buildx imagetools inspect ${{ env.NAMESPACE }}/${{ env.GETH_IMAGE_NAME }}:${{ steps.meta.outputs.version }}\n\n  merge-reth:\n    runs-on: ubuntu-latest\n    needs:\n      - reth\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Download digests\n        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0\n        with:\n          path: ${{ runner.temp }}/digests\n          pattern: digests-reth-*\n          merge-multiple: true\n\n      - name: Log into the Container registry\n        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Extract metadata for the Docker image\n        id: meta\n        uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0\n        with:\n          images: |\n            ${{ env.NAMESPACE }}/${{ env.RETH_IMAGE_NAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=tag\n            type=sha,format=long\n\n      - name: Create manifest list and push\n        working-directory: ${{ runner.temp }}/digests\n        run: |\n          docker buildx imagetools create $(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\") \\\n            $(printf '${{ env.NAMESPACE }}/${{ env.RETH_IMAGE_NAME }}@sha256:%s ' *)\n\n      - name: Inspect image\n        run: |\n          docker buildx imagetools inspect ${{ env.NAMESPACE }}/${{ env.RETH_IMAGE_NAME }}:${{ steps.meta.outputs.version }}\n\n  merge-nethermind:\n    runs-on: ubuntu-latest\n    needs:\n      - nethermind\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Download digests\n        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0\n        with:\n          path: ${{ runner.temp }}/digests\n          pattern: digests-nethermind-*\n          merge-multiple: true\n\n      - name: Log into the Container registry\n        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Extract metadata for the Docker image\n        id: meta\n        uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0\n        with:\n          images: |\n            ${{ env.NAMESPACE }}/${{ env.NETHERMIND_IMAGE_NAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=tag\n            type=sha,format=long\n\n      - name: Create manifest list and push\n        working-directory: ${{ runner.temp }}/digests\n        run: |\n          docker buildx imagetools create $(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\") \\\n            $(printf '${{ env.NAMESPACE }}/${{ env.NETHERMIND_IMAGE_NAME }}@sha256:%s ' *)\n\n      - name: Inspect image\n        run: |\n          docker buildx imagetools inspect ${{ env.NAMESPACE }}/${{ env.NETHERMIND_IMAGE_NAME }}:${{ steps.meta.outputs.version }}\n"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: Pull Request\n\non:\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  geth:\n    strategy:\n      matrix:\n        settings:\n          - arch: linux/amd64\n            runs-on: ubuntu-24.04\n          - arch: linux/arm64\n            runs-on: ubuntu-24.04-arm\n    runs-on: ${{ matrix.settings.runs-on }}\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n\n      - name: Build the Docker image\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0\n        with:\n          context: .\n          file: geth/Dockerfile\n          push: false\n          platforms: ${{ matrix.settings.arch }}\n\n  reth:\n    strategy:\n      matrix:\n        settings:\n          - arch: linux/amd64\n            runs-on: ubuntu-24.04\n            features: jemalloc,asm-keccak,optimism\n          - arch: linux/arm64\n            runs-on: ubuntu-24.04-arm\n            features: jemalloc,optimism\n    runs-on: ${{ matrix.settings.runs-on}}\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n      - name: Build the Docker image\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0\n        with:\n          context: .\n          file: reth/Dockerfile\n          push: false\n          build-args: |\n            FEATURES=${{ matrix.settings.features }}\n          platforms: ${{ matrix.settings.arch }}\n\n  nethermind:\n    strategy:\n      matrix:\n        settings:\n          - arch: linux/amd64\n            runs-on: ubuntu-24.04\n          - arch: linux/arm64\n            runs-on: ubuntu-24.04-arm\n    runs-on: ${{ matrix.settings.runs-on}}\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1\n      - name: Build the Docker image\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0\n        with:\n          context: .\n          file: nethermind/Dockerfile\n          push: false\n          platforms: ${{ matrix.settings.arch }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale issues and PRs\n\non:\n  schedule:\n    - cron: '30 0 * * *'\n  workflow_dispatch:\npermissions:\n  contents: read\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    permissions:\n      actions: write\n      issues: write\n      pull-requests: write\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2\n        with:\n          egress-policy: audit\n\n      - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0\n        with:\n          days-before-stale: 14\n          days-before-close: 5\n          stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'\n          stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'\n          close-issue-message: 'This issue was closed because it has been inactive for 5 days since being marked as stale.'\n          close-pr-message: 'This pull request was closed because it has been inactive for 5 days since being marked as stale.'"
  },
  {
    "path": ".github/workflows/update-dependencies.yml",
    "content": "name: Update Dockerfile Dependencies\non:\n  schedule:\n    - cron: '0 13 * * *'\n  workflow_dispatch:\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  update:\n    name: update\n    runs-on: ubuntu-latest\n    steps:\n      - name: Harden the runner (Audit all outbound calls)\n        uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2\n        with:\n          egress-policy: audit\n\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n\n      - name: build dependency updater\n        run: cd dependency_updater && go build\n\n      - name: run dependency updater\n        id: run_dependency_updater\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: cd dependency_updater && ./dependency_updater --repo ../ --github-action true\n\n      - name: create pull request\n        if: ${{ steps.run_dependency_updater.outputs.TITLE != '' }}\n        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8\n        with:\n          title: ${{ steps.run_dependency_updater.outputs.TITLE }}\n          commit-message: ${{ steps.run_dependency_updater.outputs.TITLE }}\n          body: \"${{ steps.run_dependency_updater.outputs.DESC }}\"\n          branch: run-dependency-updater\n          delete-branch: true"
  },
  {
    "path": ".gitignore",
    "content": "/.idea/\n/geth-data/\n/reth-data/\n/nethermind-data/\n/dependency_updater/dependency_updater\n.DS_Store"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Base Node\n\n## Code of Conduct\n\nAll interactions with this project follow our [Code of Conduct][code-of-conduct].\nBy participating, you are expected to honor this code. Violators can be banned\nfrom further participation in this project, or potentially all Base and/or\nCoinbase\nprojects.\n\n[code-of-conduct]: https://github.com/coinbase/code-of-conduct\n\n## Bug Reports\n\n* Ensure your issue [has not already been reported][1]. It may already be fixed!\n* Include the steps you carried out to produce the problem.\n* Include the behavior you observed along with the behavior you expected, and\n  why you expected it.\n* Include any relevant stack traces or debugging output.\n\n## Feature Requests\n\nWe welcome feedback with or without pull requests. If you have an idea for how\nto improve the project, great! All we ask is that you take the time to write a\nclear and concise explanation of what need you are trying to solve. If you have\nthoughts on _how_ it can be solved, include those too!\n\nThe best way to see a feature added, however, is to submit a pull request.\n\n## Pull Requests\n\n* Before creating your pull request, it's usually worth asking if the code\n  you're planning on writing will actually be considered for merging. You can\n  do this by [opening an issue][1] and asking. It may also help give the\n  maintainers context for when the time comes to review your code.\n\n* Ensure your [commit messages are well-written][2]. This can double as your\n  pull request message, so it pays to take the time to write a clear message.\n\n* Add tests for your feature. You should be able to look at other tests for\n  examples. If you're unsure, don't hesitate to [open an issue][1] and ask!\n\n* Submit your pull request!\n\n## Support Requests\n\nFor security reasons, any communication referencing support tickets for Coinbase\nproducts will be ignored. The request will have its content redacted and will\nbe locked to prevent further discussion.\n\nAll support requests must be made via [our support team][3].\n\n[1]: https://github.com/base/node/issues\n[2]: https://medium.com/brigade-engineering/the-secrets-to-great-commit-messages-106fc0a92a25\n[3]: https://support.coinbase.com/customer/en/portal/articles/2288496-how-can-i-contact-coinbase-support-\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023-2025 base.org contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![Base](logo.webp)\n\n# Base Node\n\nBase is a secure, low-cost, developer-friendly Ethereum L2 built on Optimism's [OP Stack](https://docs.optimism.io/). This repository contains Docker builds to run your own node on the Base network.\n\n[![Website base.org](https://img.shields.io/website-up-down-green-red/https/base.org.svg)](https://base.org)\n[![Docs](https://img.shields.io/badge/docs-up-green)](https://docs.base.org/)\n[![Discord](https://img.shields.io/discord/1067165013397213286?label=discord)](https://base.org/discord)\n[![Twitter Base](https://img.shields.io/twitter/follow/Base?style=social)](https://x.com/Base)\n[![Farcaster Base](https://img.shields.io/badge/Farcaster_Base-3d8fcc)](https://farcaster.xyz/base)\n\n## Quick Start\n\n1. Ensure you have an Ethereum L1 full node RPC available\n2. Choose your network:\n   - For mainnet: Use `.env.mainnet`\n   - For testnet: Use `.env.sepolia`\n3. Configure your L1 endpoints in the appropriate `.env` file:\n   ```bash\n   OP_NODE_L1_ETH_RPC=<your-preferred-l1-rpc>\n   OP_NODE_L1_BEACON=<your-preferred-l1-beacon>\n   OP_NODE_L1_BEACON_ARCHIVER=<your-preferred-l1-beacon-archiver>\n   ```\n4. Start the node:\n\n   ```bash\n   # For mainnet (default):\n   docker compose up --build\n\n   # For testnet:\n   NETWORK_ENV=.env.sepolia docker compose up --build\n\n   # To use a specific client (optional):\n   CLIENT=reth docker compose up --build\n\n   # For testnet with a specific client:\n   NETWORK_ENV=.env.sepolia CLIENT=reth docker compose up --build\n   ```\n\n### Supported Clients\n\n- `reth` (default)\n- `geth`\n- `nethermind`\n\n## Requirements\n\n### Minimum Requirements\n\n- Modern Multicore CPU\n- 32GB RAM (64GB Recommended)\n- NVMe SSD drive\n- Storage: (2 \\* [current chain size](https://base.org/stats) + [snapshot size](https://basechaindata.vercel.app) + 20% buffer) (to accommodate future growth)\n- Docker and Docker Compose\n\n### Production Hardware Specifications\n\nThe following are the hardware specifications we use in production:\n\n#### Reth Archive Node (recommended)\n\n- **Instance**: AWS i7i.12xlarge\n- **Storage**: RAID 0 of all local NVMe drives (`/dev/nvme*`)\n- **Filesystem**: ext4\n\n#### Geth Full Node\n\n- **Instance**: AWS i7i.12xlarge\n- **Storage**: RAID 0 of all local NVMe drives (`/dev/nvme*`)\n- **Filesystem**: ext4\n\n> [!NOTE]\nTo run the node using a supported client, you can use the following command:\n`CLIENT=supported_client docker compose up --build`\n \nSupported clients:\n - reth (runs vanilla node by default, Flashblocks mode enabled by providing RETH_FB_WEBSOCKET_URL, see [Reth Node README](./reth/README.md))\n - geth\n - nethermind\n\n## Configuration\n\n### Required Settings\n\n- L1 Configuration:\n  - `OP_NODE_L1_ETH_RPC`: Your Ethereum L1 node RPC endpoint\n  - `OP_NODE_L1_BEACON`: Your L1 beacon node endpoint\n  - `OP_NODE_L1_BEACON_ARCHIVER`: Your L1 beacon archiver endpoint\n  - `OP_NODE_L1_RPC_KIND`: The type of RPC provider being used (default: \"debug_geth\"). Supported values:\n    - `alchemy`: Alchemy RPC provider\n    - `quicknode`: QuickNode RPC provider\n    - `infura`: Infura RPC provider\n    - `parity`: Parity RPC provider\n    - `nethermind`: Nethermind RPC provider\n    - `debug_geth`: Debug Geth RPC provider\n    - `erigon`: Erigon RPC provider\n    - `basic`: Basic RPC provider (standard receipt fetching only)\n    - `any`: Any available RPC method\n    - `standard`: Standard RPC methods including newer optimized methods\n\n### Network Settings\n\n- Mainnet:\n  - `RETH_CHAIN=base`\n  - `OP_NODE_NETWORK=base-mainnet`\n  - Sequencer: `https://mainnet-sequencer.base.org`\n\n### Performance Settings\n\n- Cache Settings:\n  - `GETH_CACHE=\"20480\"` (20GB)\n  - `GETH_CACHE_DATABASE=\"20\"` (4GB)\n  - `GETH_CACHE_GC=\"12\"`\n  - `GETH_CACHE_SNAPSHOT=\"24\"`\n  - `GETH_CACHE_TRIE=\"44\"`\n\n### Optional Features\n\n- EthStats Monitoring (uncomment to enable)\n- Trusted RPC Mode (uncomment to enable)\n- Snap Sync (experimental)\n\nFor full configuration options, see the `.env.mainnet` file.\n\n## Snapshots\n\nSnapshots are available to help you sync your node more quickly. See [docs.base.org](https://docs.base.org/chain/run-a-base-node#snapshots) for links and more details on how to restore from a snapshot.\n\n## Supported Networks\n\n| Network | Status |\n| ------- | ------ |\n| Mainnet | ✅     |\n| Testnet | ✅     |\n\n## Troubleshooting\n\nFor support please join our [Discord](https://discord.gg/buildonbase) post in `🛠｜node-operators`. You can alternatively open a new GitHub issue.\n\n## Disclaimer\n\nTHE NODE SOFTWARE IS PROVIDED \"AS IS\" WITHOUT WARRANTY OF ANY KIND. We make no guarantees about asset protection or security. Usage is subject to applicable laws and regulations.\n\nFor more information, visit [docs.base.org](https://docs.base.org/).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security\n\n## Bug bounty program\n\nIn line with our strategy of being the safest way for users to access crypto:\n\n+ Coinbase will be extending our [best-in-industry][1] million-dollar [HackerOne bug bounty program][2]\nto cover the Base network, the Base bridge contracts, and Base infrastructure.\n\n+ Coinbase will be working in tandem with OP Labs to harden the security\nguarantees of Bedrock and accelerate the timeline for decentralized\nfault-proofs on the [OP Stack][3].\n\n+ Coinbase's bug bounty program will run alongside Optimism's existing [Immunefi Bedrock bounty program][4]\nto support the open source [Bedrock][5] OP Stack framework.\n\n## Reporting vulnerabilities\n\nAll potential vulnerability reports can be submitted via the [HackerOne][6]\nplatform.\n\nThe HackerOne platform allows us to have a centralized and single reporting\nsource for us to deliver optimized SLA's and results. All reports submitted to\nthe platform are triaged around the clock by our team of Coinbase engineers\nwith domain knowledge, ensuring the best quality of review.\n\nFor more information on reporting vulnerabilities and our HackerOne bug bounty\nprogram, view our [security program policies][7].\n\n[1]: https://www.coinbase.com/blog/celebrating-10-years-of-our-bug-bounty-program\n[2]: https://hackerone.com/coinbase?type=team \n[3]: https://docs.optimism.io/\n[4]: https://immunefi.com/bounty/optimism/\n[5]: https://docs.optimism.io/docs/releases/bedrock/\n[6]: https://hackerone.com/coinbase\n[7]: https://hackerone.com/coinbase?view_policy=true\n\n\n"
  },
  {
    "path": "dependency_updater/dependency_updater.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/ethereum-optimism/optimism/op-service/retry\"\n\t\"github.com/google/go-github/v72/github\"\n\t\"github.com/urfave/cli/v3\"\n\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\ntype Info struct {\n\tTag       string `json:\"tag,omitempty\"`\n\tCommit    string `json:\"commit\"`\n\tTagPrefix string `json:\"tagPrefix,omitempty\"`\n\tOwner     string `json:\"owner\"`\n\tRepo      string `json:\"repo\"`\n\tBranch    string `json:\"branch,omitempty\"`\n\tTracking  string `json:\"tracking\"`\n}\n\ntype VersionUpdateInfo struct {\n\tRepo    string\n\tFrom    string\n\tTo      string\n\tDiffUrl string\n}\n\ntype Dependencies = map[string]*Info\n\nfunc main() {\n\tcmd := &cli.Command{\n\t\tName:  \"updater\",\n\t\tUsage: \"Updates the dependencies in the geth, nethermind and reth Dockerfiles\",\n\t\tFlags: []cli.Flag{\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:     \"token\",\n\t\t\t\tUsage:    \"Auth token used to make requests to the Github API must be set using export\",\n\t\t\t\tSources:  cli.EnvVars(\"GITHUB_TOKEN\"),\n\t\t\t\tRequired: true,\n\t\t\t},\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:     \"repo\",\n\t\t\t\tUsage:    \"Specifies repo location to run the version updater on\",\n\t\t\t\tRequired: true,\n\t\t\t},\n\t\t\t&cli.BoolFlag{\n\t\t\t\tName:     \"commit\",\n\t\t\t\tUsage:    \"Stages updater changes and creates commit message\",\n\t\t\t\tRequired: false,\n\t\t\t},\n\t\t\t&cli.BoolFlag{\n\t\t\t\tName:     \"github-action\",\n\t\t\t\tUsage:    \"Specifies whether tool is being used through github action workflow\",\n\t\t\t\tRequired: false,\n\t\t\t},\n\t\t},\n\t\tAction: func(ctx context.Context, cmd *cli.Command) error {\n\t\t\terr := updater(cmd.String(\"token\"), cmd.String(\"repo\"), cmd.Bool(\"commit\"), cmd.Bool(\"github-action\"))\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to run updater: %s\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tif err := cmd.Run(context.Background(), os.Args); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc updater(token string, repoPath string, commit bool, githubAction bool) error {\n\tvar err error\n\tvar dependencies Dependencies\n\tvar updatedDependencies []VersionUpdateInfo\n\n\tf, err := os.ReadFile(repoPath + \"/versions.json\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading versions JSON: %s\", err)\n\t}\n\n\tclient := github.NewClient(nil).WithAuthToken(token)\n\tctx := context.Background()\n\n\terr = json.Unmarshal(f, &dependencies)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling versions JSON to dependencies: %s\", err)\n\t}\n\n\tfor dependency := range dependencies {\n\t\tvar updatedDependency VersionUpdateInfo\n\t\terr := retry.Do0(context.Background(), 3, retry.Fixed(1*time.Second), func() error {\n\t\t\tupdatedDependency, err = getAndUpdateDependency(\n\t\t\t\tctx,\n\t\t\t\tclient,\n\t\t\t\tdependency,\n\t\t\t\trepoPath,\n\t\t\t\tdependencies,\n\t\t\t)\n\t\t\treturn err\n\t\t})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting and updating version/commit for \"+dependency+\": %s\", err)\n\t\t}\n\n\t\tif updatedDependency != (VersionUpdateInfo{}) {\n\t\t\tupdatedDependencies = append(updatedDependencies, updatedDependency)\n\t\t}\n\t}\n\n\te := createVersionsEnv(repoPath, dependencies)\n\tif e != nil {\n\t\treturn fmt.Errorf(\"error creating versions.env: %s\", e)\n\t}\n\n\tif (commit && updatedDependencies != nil) || (githubAction && updatedDependencies != nil) {\n\t\terr := createCommitMessage(updatedDependencies, repoPath, githubAction)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error creating commit message: %s\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc createCommitMessage(updatedDependencies []VersionUpdateInfo, repoPath string, githubAction bool) error {\n\tvar repos []string\n\tdescriptionLines := []string{\n\t\t\"### Dependency Updates\",\n\t}\n\n\tcommitTitle := \"chore: updated \"\n\n\tfor _, dependency := range updatedDependencies {\n\t\trepo, tag := dependency.Repo, dependency.To\n\t\tdescriptionLines = append(descriptionLines, fmt.Sprintf(\"**%s** - %s:  [diff](%s)\", repo, tag, dependency.DiffUrl))\n\t\trepos = append(repos, repo)\n\t}\n\tcommitDescription := strings.Join(descriptionLines, \"\\n\")\n\tcommitTitle += strings.Join(repos, \", \")\n\n\tif githubAction {\n\t\terr := writeToGithubOutput(commitTitle, commitDescription, repoPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error creating git commit message: %s\", err)\n\t\t}\n\t} else {\n\t\tcmd := exec.Command(\"git\", \"commit\", \"-am\", commitTitle, \"-m\", commitDescription)\n\t\tif err := cmd.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to run git commit -m: %s\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getAndUpdateDependency(ctx context.Context, client *github.Client, dependencyType string, repoPath string, dependencies Dependencies) (VersionUpdateInfo, error) {\n\tversion, commit, updatedDependency, err := getVersionAndCommit(ctx, client, dependencies, dependencyType)\n\tif err != nil {\n\t\treturn VersionUpdateInfo{}, err\n\t}\n\tif updatedDependency != (VersionUpdateInfo{}) {\n\t\te := updateVersionTagAndCommit(commit, version, dependencyType, repoPath, dependencies)\n\t\tif e != nil {\n\t\t\treturn VersionUpdateInfo{}, fmt.Errorf(\"error updating version tag and commit: %s\", e)\n\t\t}\n\t}\n\n\treturn updatedDependency, nil\n}\n\nfunc getVersionAndCommit(ctx context.Context, client *github.Client, dependencies Dependencies, dependencyType string) (string, string, VersionUpdateInfo, error) {\n\tvar selectedTag *github.RepositoryTag\n\tvar commit string\n\tvar diffUrl string\n\tvar updatedDependency VersionUpdateInfo\n\toptions := &github.ListOptions{Page: 1}\n\tcurrentTag := dependencies[dependencyType].Tag\n\ttagPrefix := dependencies[dependencyType].TagPrefix\n\n\tif dependencies[dependencyType].Tracking == \"tag\" || dependencies[dependencyType].Tracking == \"release\" {\n\t\t// Collect all valid tags across all pages, then find the max version\n\t\tvar validTags []*github.RepositoryTag\n\t\ttrackingMode := dependencies[dependencyType].Tracking\n\n\t\tfor {\n\t\t\ttags, resp, err := client.Repositories.ListTags(\n\t\t\t\tctx,\n\t\t\t\tdependencies[dependencyType].Owner,\n\t\t\t\tdependencies[dependencyType].Repo,\n\t\t\t\toptions)\n\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", \"\", VersionUpdateInfo{}, fmt.Errorf(\"error getting tags: %s\", err)\n\t\t\t}\n\n\t\t\tfor _, tag := range tags {\n\t\t\t\t// Skip if tagPrefix is set and doesn't match\n\t\t\t\tif tagPrefix != \"\" && !strings.HasPrefix(*tag.Name, tagPrefix) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Filter based on tracking mode:\n\t\t\t\t// - \"release\": only stable releases (no prerelease suffix)\n\t\t\t\t// - \"tag\": releases and RC versions only (exclude -synctest, -alpha, etc.)\n\t\t\t\tif trackingMode == \"release\" {\n\t\t\t\t\tif !IsReleaseVersion(*tag.Name, tagPrefix) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t} else if trackingMode == \"tag\" {\n\t\t\t\t\tif !IsReleaseOrRCVersion(*tag.Name, tagPrefix) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Check if this is a valid upgrade (not a downgrade)\n\t\t\t\tif err := ValidateVersionUpgrade(currentTag, *tag.Name, tagPrefix); err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvalidTags = append(validTags, tag)\n\t\t\t}\n\n\t\t\tif resp.NextPage == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toptions.Page = resp.NextPage\n\t\t}\n\n\t\t// Find the maximum version among valid tags\n\t\tfor _, tag := range validTags {\n\t\t\t// Skip if this tag can't be parsed\n\t\t\tif _, err := ParseVersion(*tag.Name, tagPrefix); err != nil {\n\t\t\t\tlog.Printf(\"Skipping unparseable tag %s: %v\", *tag.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif selectedTag == nil {\n\t\t\t\tselectedTag = tag\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcmp, err := CompareVersions(*tag.Name, *selectedTag.Name, tagPrefix)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error comparing versions %s and %s: %v\", *tag.Name, *selectedTag.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif cmp > 0 {\n\t\t\t\tselectedTag = tag\n\t\t\t}\n\t\t}\n\n\t\t// If no valid version found, keep current version\n\t\tif selectedTag == nil {\n\t\t\tlog.Printf(\"No valid upgrade found for %s, keeping %s\", dependencyType, currentTag)\n\t\t\treturn currentTag, dependencies[dependencyType].Commit, VersionUpdateInfo{}, nil\n\t\t}\n\n\t\tif *selectedTag.Name != currentTag {\n\t\t\tdiffUrl = generateGithubRepoUrl(dependencies, dependencyType) + \"/compare/\" +\n\t\t\t\tcurrentTag + \"...\" + *selectedTag.Name\n\t\t}\n\n\t\t// Get commit SHA from the tag\n\t\tcommit = *selectedTag.Commit.SHA\n\t}\n\n\tif diffUrl != \"\" {\n\t\tupdatedDependency = VersionUpdateInfo{\n\t\t\tdependencies[dependencyType].Repo,\n\t\t\tdependencies[dependencyType].Tag,\n\t\t\t*selectedTag.Name,\n\t\t\tdiffUrl,\n\t\t}\n\t}\n\n\tif dependencies[dependencyType].Tracking == \"branch\" {\n\t\tbranchCommit, _, err := client.Repositories.ListCommits(\n\t\t\tctx,\n\t\t\tdependencies[dependencyType].Owner,\n\t\t\tdependencies[dependencyType].Repo,\n\t\t\t&github.CommitsListOptions{\n\t\t\t\tSHA: dependencies[dependencyType].Branch,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", VersionUpdateInfo{}, fmt.Errorf(\"error listing commits for \"+dependencyType+\": %s\", err)\n\t\t}\n\t\tcommit = *branchCommit[0].SHA\n\t\tif dependencies[dependencyType].Commit != commit {\n\t\t\tfrom, to := dependencies[dependencyType].Commit, commit\n\t\t\tdiffUrl = fmt.Sprintf(\"%s/compare/%s...%s\", generateGithubRepoUrl(dependencies, dependencyType), from, to)\n\t\t\tupdatedDependency = VersionUpdateInfo{\n\t\t\t\tdependencies[dependencyType].Repo,\n\t\t\t\tdependencies[dependencyType].Tag,\n\t\t\t\tcommit,\n\t\t\t\tdiffUrl,\n\t\t\t}\n\t\t}\n\t}\n\n\tif selectedTag != nil {\n\t\treturn *selectedTag.Name, commit, updatedDependency, nil\n\t}\n\n\treturn \"\", commit, updatedDependency, nil\n}\n\nfunc updateVersionTagAndCommit(\n\tcommit string,\n\ttag string,\n\tdependencyType string,\n\trepoPath string,\n\tdependencies Dependencies) error {\n\tdependencies[dependencyType].Tag = tag\n\tdependencies[dependencyType].Commit = commit\n\terr := writeToVersionsJson(repoPath, dependencies)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing to versions \"+dependencyType+\": %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc writeToVersionsJson(repoPath string, dependencies Dependencies) error {\n\t// formatting json\n\tupdatedJson, err := json.MarshalIndent(dependencies, \"\", \"\t  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshaling dependencies json: %s\", err)\n\t}\n\n\te := os.WriteFile(repoPath+\"/versions.json\", updatedJson, 0644)\n\tif e != nil {\n\t\treturn fmt.Errorf(\"error writing to versions.json: %s\", e)\n\t}\n\n\treturn nil\n}\n\nfunc createVersionsEnv(repoPath string, dependencies Dependencies) error {\n\tenvLines := []string{}\n\n\tfor dependency := range dependencies {\n\t\trepoUrl := generateGithubRepoUrl(dependencies, dependency) + \".git\"\n\n\t\tdependencyPrefix := strings.ToUpper(dependency)\n\n\t\tif dependencies[dependency].Tracking == \"branch\" {\n\t\t\tdependencies[dependency].Tag = dependencies[dependency].Branch\n\t\t}\n\n\t\tenvLines = append(envLines, fmt.Sprintf(\"export %s_%s=%s\",\n\t\t\tdependencyPrefix, \"TAG\", dependencies[dependency].Tag))\n\n\t\tenvLines = append(envLines, fmt.Sprintf(\"export %s_%s=%s\",\n\t\t\tdependencyPrefix, \"COMMIT\", dependencies[dependency].Commit))\n\n\t\tenvLines = append(envLines, fmt.Sprintf(\"export %s_%s=%s\",\n\t\t\tdependencyPrefix, \"REPO\", repoUrl))\n\t}\n\n\tslices.Sort(envLines)\n\n\tfile, err := os.Create(repoPath + \"/versions.env\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating versions.env file: %s\", err)\n\t}\n\tdefer file.Close()\n\n\t_, err = file.WriteString(strings.Join(envLines, \"\\n\"))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing to versions.env file: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc writeToGithubOutput(title string, description string, repoPath string) error {\n\tfile := os.Getenv(\"GITHUB_OUTPUT\")\n\tf, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open GITHUB_OUTPUT file: %s\", err)\n\t}\n\tdefer f.Close()\n\n\ttitleToWrite := fmt.Sprintf(\"%s=%s\\n\", \"TITLE\", title)\n\t_, err = f.WriteString(titleToWrite)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to GITHUB_OUTPUT file: %s\", err)\n\t}\n\n\tdelimiter := \"EOF\"\n\tdescToWrite := fmt.Sprintf(\"%s<<%s\\n%s\\n%s\\n\", \"DESC\", delimiter, description, delimiter)\n\t_, err = f.WriteString(descToWrite)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to GITHUB_OUTPUT file: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc generateGithubRepoUrl(dependencies Dependencies, dependencyType string) string {\n\treturn \"https://github.com/\" + dependencies[dependencyType].Owner + \"/\" + dependencies[dependencyType].Repo\n}\n"
  },
  {
    "path": "dependency_updater/go.mod",
    "content": "module github.com/base/node/dependency_updater\n\ngo 1.24.3\n\nrequire (\n\tgithub.com/ethereum-optimism/optimism v1.13.3\n\tgithub.com/google/go-github/v72 v72.0.0\n\tgithub.com/urfave/cli/v3 v3.3.8\n)\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n)\n"
  },
  {
    "path": "dependency_updater/go.sum",
    "content": "github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/ethereum-optimism/optimism v1.13.3 h1:rfPx7OembMnoEASU1ozA/Foa7Am7UA+h0SB+OUrxn7s=\ngithub.com/ethereum-optimism/optimism v1.13.3/go.mod h1:WrVFtk3cP45tvHs7MARn9KGQu35XIoXo/IOWU6K/rzk=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=\ngithub.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/urfave/cli/v3 v3.3.8 h1:BzolUExliMdet9NlJ/u4m5vHSotJ3PzEqSAZ1oPMa/E=\ngithub.com/urfave/cli/v3 v3.3.8/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "dependency_updater/version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n)\n\n// rcPattern matches various RC formats: -rc1, -rc.1, -rc-1, -RC1, etc.\nvar rcPattern = regexp.MustCompile(`(?i)-rc[.-]?(\\d+)`)\n\n// rcOnlyPattern is used to check if a version contains ONLY an RC prerelease (not -synctest, -alpha, etc.)\nvar rcOnlyPattern = regexp.MustCompile(`(?i)^-rc[.-]?\\d+$`)\n\n// ParseVersion extracts and normalizes a semantic version from a tag string.\n// It handles tagPrefix stripping, v-prefix normalization, and RC format normalization.\nfunc ParseVersion(tag string, tagPrefix string) (*semver.Version, error) {\n\tversionStr := tag\n\n\t// Step 1: Strip tagPrefix if present (e.g., \"op-node/v1.16.2\" -> \"v1.16.2\")\n\tif tagPrefix != \"\" && strings.HasPrefix(tag, tagPrefix) {\n\t\tversionStr = strings.TrimPrefix(tag, tagPrefix)\n\t\tversionStr = strings.TrimPrefix(versionStr, \"/\")\n\t}\n\n\t// Step 2: Normalize RC formats to semver-compatible format\n\t// \"-rc1\" -> \"-rc.1\", \"-rc-1\" -> \"-rc.1\"\n\tversionStr = normalizeRCFormat(versionStr)\n\n\t// Step 3: Parse using Masterminds/semver (handles v prefix automatically)\n\tv, err := semver.NewVersion(versionStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid version format %q: %w\", tag, err)\n\t}\n\n\treturn v, nil\n}\n\n// normalizeRCFormat converts various RC formats to semver-compatible format.\n// Examples: \"-rc1\" -> \"-rc.1\", \"-rc-2\" -> \"-rc.2\"\nfunc normalizeRCFormat(version string) string {\n\treturn rcPattern.ReplaceAllString(version, \"-rc.$1\")\n}\n\n// ValidateVersionUpgrade checks if transitioning from currentTag to newTag\n// is a valid upgrade (not a downgrade).\n// Returns nil if valid, error explaining why if invalid.\nfunc ValidateVersionUpgrade(currentTag, newTag, tagPrefix string) error {\n\t// First-time setup: no current version, any valid version is acceptable\n\tif currentTag == \"\" {\n\t\t_, err := ParseVersion(newTag, tagPrefix)\n\t\treturn err\n\t}\n\n\t// Parse current version\n\tcurrentVersion, err := ParseVersion(currentTag, tagPrefix)\n\tif err != nil {\n\t\t// Current version unparseable - still validate new version is parseable\n\t\t_, newErr := ParseVersion(newTag, tagPrefix)\n\t\treturn newErr\n\t}\n\n\t// Parse new version\n\tnewVersion, err := ParseVersion(newTag, tagPrefix)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"new version %q is not a valid semver: %w\", newTag, err)\n\t}\n\n\t// Check for downgrade\n\tif newVersion.LessThan(currentVersion) {\n\t\treturn fmt.Errorf(\n\t\t\t\"version downgrade detected: %s -> %s\",\n\t\t\tcurrentTag, newTag,\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// CompareVersions compares two version tags and returns:\n// -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2\n// Returns 0 and error if either version cannot be parsed.\nfunc CompareVersions(v1Tag, v2Tag, tagPrefix string) (int, error) {\n\tv1, err := ParseVersion(v1Tag, tagPrefix)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tv2, err := ParseVersion(v2Tag, tagPrefix)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn v1.Compare(v2), nil\n}\n\n// IsReleaseVersion returns true if the tag is a stable release (no prerelease suffix).\n// Examples:\n//   - \"v1.0.0\" -> true\n//   - \"v1.0.0-rc1\" -> false\n//   - \"v1.0.0-synctest.0\" -> false\nfunc IsReleaseVersion(tag string, tagPrefix string) bool {\n\tv, err := ParseVersion(tag, tagPrefix)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn v.Prerelease() == \"\"\n}\n\n// IsRCVersion returns true if the tag is a release candidate version.\n// This matches versions with -rc, -rc.N, -rc-N, -rcN suffixes.\n// Examples:\n//   - \"v1.0.0-rc1\" -> true\n//   - \"v1.0.0-rc.2\" -> true\n//   - \"v1.0.0\" -> false (stable release, not RC)\n//   - \"v1.0.0-synctest.0\" -> false (not an RC)\n//   - \"v1.0.0-alpha\" -> false (not an RC)\nfunc IsRCVersion(tag string, tagPrefix string) bool {\n\tv, err := ParseVersion(tag, tagPrefix)\n\tif err != nil {\n\t\treturn false\n\t}\n\tprerelease := v.Prerelease()\n\tif prerelease == \"\" {\n\t\treturn false\n\t}\n\t// Check if the prerelease is ONLY an RC format (e.g., \"rc.1\", \"rc1\", \"rc-1\")\n\t// We need to check the original format before normalization\n\treturn rcOnlyPattern.MatchString(\"-\" + prerelease)\n}\n\n// IsReleaseOrRCVersion returns true if the tag is either a stable release or an RC version.\n// This excludes other prereleases like -alpha, -beta, -synctest, etc.\nfunc IsReleaseOrRCVersion(tag string, tagPrefix string) bool {\n\treturn IsReleaseVersion(tag, tagPrefix) || IsRCVersion(tag, tagPrefix)\n}\n"
  },
  {
    "path": "dependency_updater/version_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNormalizeRCFormat(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\"v0.3.0-rc1\", \"v0.3.0-rc.1\"},\n\t\t{\"v0.3.0-rc.1\", \"v0.3.0-rc.1\"},\n\t\t{\"v0.3.0-rc-1\", \"v0.3.0-rc.1\"},\n\t\t{\"v0.3.0-RC1\", \"v0.3.0-rc.1\"},\n\t\t{\"v0.3.0-rc12\", \"v0.3.0-rc.12\"},\n\t\t{\"v0.3.0\", \"v0.3.0\"},\n\t\t{\"v0.3.0-alpha\", \"v0.3.0-alpha\"},\n\t\t{\"v0.3.0-beta.1\", \"v0.3.0-beta.1\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tresult := normalizeRCFormat(tt.input)\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"normalizeRCFormat(%q) = %q, want %q\", tt.input, result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseVersion(t *testing.T) {\n\ttests := []struct {\n\t\ttag       string\n\t\ttagPrefix string\n\t\twantErr   bool\n\t}{\n\t\t// Standard versions\n\t\t{\"v0.2.2\", \"\", false},\n\t\t{\"v0.3.0\", \"\", false},\n\t\t{\"1.35.3\", \"\", false}, // nethermind style - no v prefix\n\n\t\t// RC versions\n\t\t{\"v0.3.0-rc1\", \"\", false},\n\t\t{\"v0.3.0-rc.1\", \"\", false},\n\t\t{\"v0.3.0-rc-1\", \"\", false},\n\t\t{\"v0.3.0-rc.2\", \"\", false},\n\n\t\t// With tagPrefix\n\t\t{\"op-node/v1.16.2\", \"op-node\", false},\n\t\t{\"op-node/v1.16.3-rc1\", \"op-node\", false},\n\n\t\t// Non-standard but parseable\n\t\t{\"v1.101603.5\", \"\", false}, // op-geth style\n\n\t\t// Invalid\n\t\t{\"not-a-version\", \"\", true},\n\t\t{\"\", \"\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.tag, func(t *testing.T) {\n\t\t\t_, err := ParseVersion(tt.tag, tt.tagPrefix)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseVersion(%q, %q) error = %v, wantErr %v\", tt.tag, tt.tagPrefix, err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestValidateVersionUpgrade(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tcurrentTag string\n\t\tnewTag     string\n\t\ttagPrefix  string\n\t\twantErr    bool\n\t}{\n\t\t// Valid upgrades\n\t\t{\"stable to rc\", \"v0.2.2\", \"v0.3.0-rc1\", \"\", false},\n\t\t{\"rc to rc\", \"v0.3.0-rc1\", \"v0.3.0-rc2\", \"\", false},\n\t\t{\"rc to stable\", \"v0.3.0-rc2\", \"v0.3.0\", \"\", false},\n\t\t{\"stable to stable\", \"v0.2.2\", \"v0.3.0\", \"\", false},\n\t\t{\"patch upgrade\", \"v0.2.2\", \"v0.2.3\", \"\", false},\n\t\t{\"minor upgrade\", \"v0.2.2\", \"v0.3.0\", \"\", false},\n\t\t{\"major upgrade\", \"v0.2.2\", \"v1.0.0\", \"\", false},\n\t\t{\"same version\", \"v0.2.2\", \"v0.2.2\", \"\", false},\n\n\t\t// With tagPrefix\n\t\t{\"prefix upgrade\", \"op-node/v1.16.2\", \"op-node/v1.16.3\", \"op-node\", false},\n\t\t{\"prefix rc upgrade\", \"op-node/v1.16.2\", \"op-node/v1.17.0-rc1\", \"op-node\", false},\n\n\t\t// No v prefix (nethermind style)\n\t\t{\"no v prefix upgrade\", \"1.35.3\", \"1.35.4\", \"\", false},\n\n\t\t// Invalid downgrades\n\t\t{\"downgrade major\", \"v0.3.0\", \"v0.2.2\", \"\", true},\n\t\t{\"downgrade minor\", \"v0.3.0\", \"v0.2.9\", \"\", true},\n\t\t{\"downgrade patch\", \"v0.3.1\", \"v0.3.0\", \"\", true},\n\t\t{\"stable to rc same version\", \"v0.3.0\", \"v0.3.0-rc2\", \"\", true},\n\t\t{\"stable to rc older version\", \"v0.3.0\", \"v0.2.0-rc1\", \"\", true},\n\n\t\t// Edge cases\n\t\t{\"empty current - valid new\", \"\", \"v0.3.0\", \"\", false},\n\t\t{\"empty current - invalid new\", \"\", \"not-a-version\", \"\", true},\n\t\t{\"unparseable current allows update\", \"not-semver\", \"v0.3.0\", \"\", false},\n\n\t\t// Unparseable current with unparseable new should fail\n\t\t{\"unparseable current - unparseable new\", \"rollup-boost/v0.7.11\", \"websocket-proxy/v0.0.2\", \"\", true},\n\t\t{\"unparseable current - valid new\", \"rollup-boost/v0.7.11\", \"v0.8.0\", \"\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := ValidateVersionUpgrade(tt.currentTag, tt.newTag, tt.tagPrefix)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ValidateVersionUpgrade(%q, %q, %q) error = %v, wantErr %v\",\n\t\t\t\t\ttt.currentTag, tt.newTag, tt.tagPrefix, err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCompareVersions(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tv1        string\n\t\tv2        string\n\t\ttagPrefix string\n\t\twant      int\n\t}{\n\t\t{\"v1 less than v2\", \"v0.2.2\", \"v0.3.0\", \"\", -1},\n\t\t{\"v1 greater than v2\", \"v0.3.0\", \"v0.2.2\", \"\", 1},\n\t\t{\"equal versions\", \"v0.3.0\", \"v0.3.0\", \"\", 0},\n\t\t{\"rc less than stable\", \"v0.3.0-rc1\", \"v0.3.0\", \"\", -1},\n\t\t{\"rc1 less than rc2\", \"v0.3.0-rc1\", \"v0.3.0-rc2\", \"\", -1},\n\t\t{\"stable greater than rc\", \"v0.3.0\", \"v0.3.0-rc2\", \"\", 1},\n\t\t{\"with prefix\", \"op-node/v1.16.2\", \"op-node/v1.16.3\", \"op-node\", -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := CompareVersions(tt.v1, tt.v2, tt.tagPrefix)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"CompareVersions(%q, %q, %q) unexpected error: %v\", tt.v1, tt.v2, tt.tagPrefix, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"CompareVersions(%q, %q, %q) = %d, want %d\", tt.v1, tt.v2, tt.tagPrefix, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsReleaseVersion(t *testing.T) {\n\ttests := []struct {\n\t\ttag       string\n\t\ttagPrefix string\n\t\twant      bool\n\t}{\n\t\t// Stable releases\n\t\t{\"v1.0.0\", \"\", true},\n\t\t{\"v0.2.2\", \"\", true},\n\t\t{\"1.35.3\", \"\", true}, // nethermind style\n\t\t{\"v1.101603.5\", \"\", true}, // op-geth style\n\n\t\t// With prefix\n\t\t{\"op-node/v1.16.2\", \"op-node\", true},\n\n\t\t// Pre-release versions (should return false)\n\t\t{\"v1.0.0-rc1\", \"\", false},\n\t\t{\"v1.0.0-rc.1\", \"\", false},\n\t\t{\"v1.0.0-rc-1\", \"\", false},\n\t\t{\"v1.0.0-synctest.0\", \"\", false},\n\t\t{\"v1.0.0-alpha\", \"\", false},\n\t\t{\"v1.0.0-beta.1\", \"\", false},\n\t\t{\"op-node/v1.16.6-synctest.0\", \"op-node\", false},\n\t\t{\"op-node/v1.16.3-rc1\", \"op-node\", false},\n\n\t\t// Invalid versions (should return false)\n\t\t{\"not-a-version\", \"\", false},\n\t\t{\"\", \"\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.tag, func(t *testing.T) {\n\t\t\tgot := IsReleaseVersion(tt.tag, tt.tagPrefix)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"IsReleaseVersion(%q, %q) = %v, want %v\", tt.tag, tt.tagPrefix, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsRCVersion(t *testing.T) {\n\ttests := []struct {\n\t\ttag       string\n\t\ttagPrefix string\n\t\twant      bool\n\t}{\n\t\t// RC versions\n\t\t{\"v1.0.0-rc1\", \"\", true},\n\t\t{\"v1.0.0-rc.1\", \"\", true},\n\t\t{\"v1.0.0-rc-1\", \"\", true},\n\t\t{\"v1.0.0-RC1\", \"\", true},\n\t\t{\"v1.0.0-rc12\", \"\", true},\n\t\t{\"op-node/v1.16.3-rc1\", \"op-node\", true},\n\t\t{\"op-node/v1.16.3-rc.2\", \"op-node\", true},\n\n\t\t// Stable releases (not RC)\n\t\t{\"v1.0.0\", \"\", false},\n\t\t{\"v0.2.2\", \"\", false},\n\t\t{\"op-node/v1.16.2\", \"op-node\", false},\n\n\t\t// Other pre-release versions (not RC)\n\t\t{\"v1.0.0-synctest.0\", \"\", false},\n\t\t{\"op-node/v1.16.6-synctest.0\", \"op-node\", false},\n\t\t{\"v1.0.0-alpha\", \"\", false},\n\t\t{\"v1.0.0-beta.1\", \"\", false},\n\t\t{\"v1.0.0-alpha.rc1\", \"\", false}, // rc is part of another prerelease\n\n\t\t// Invalid versions\n\t\t{\"not-a-version\", \"\", false},\n\t\t{\"\", \"\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.tag, func(t *testing.T) {\n\t\t\tgot := IsRCVersion(tt.tag, tt.tagPrefix)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"IsRCVersion(%q, %q) = %v, want %v\", tt.tag, tt.tagPrefix, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsReleaseOrRCVersion(t *testing.T) {\n\ttests := []struct {\n\t\ttag       string\n\t\ttagPrefix string\n\t\twant      bool\n\t}{\n\t\t// Stable releases - should pass\n\t\t{\"v1.0.0\", \"\", true},\n\t\t{\"v0.2.2\", \"\", true},\n\t\t{\"op-node/v1.16.2\", \"op-node\", true},\n\n\t\t// RC versions - should pass\n\t\t{\"v1.0.0-rc1\", \"\", true},\n\t\t{\"v1.0.0-rc.1\", \"\", true},\n\t\t{\"op-node/v1.16.3-rc1\", \"op-node\", true},\n\n\t\t// Other pre-release versions - should NOT pass\n\t\t{\"v1.0.0-synctest.0\", \"\", false},\n\t\t{\"op-node/v1.16.6-synctest.0\", \"op-node\", false},\n\t\t{\"v1.0.0-alpha\", \"\", false},\n\t\t{\"v1.0.0-beta.1\", \"\", false},\n\n\t\t// Invalid versions\n\t\t{\"not-a-version\", \"\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.tag, func(t *testing.T) {\n\t\t\tgot := IsReleaseOrRCVersion(tt.tag, tt.tagPrefix)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"IsReleaseOrRCVersion(%q, %q) = %v, want %v\", tt.tag, tt.tagPrefix, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRCVersionOrdering(t *testing.T) {\n\t// Verify that RC versions are ordered correctly\n\tversions := []string{\n\t\t\"v0.2.2\",\n\t\t\"v0.3.0-rc.1\",\n\t\t\"v0.3.0-rc.2\",\n\t\t\"v0.3.0\",\n\t\t\"v0.3.1\",\n\t}\n\n\tfor i := 0; i < len(versions)-1; i++ {\n\t\tcurrent := versions[i]\n\t\tnext := versions[i+1]\n\t\tt.Run(current+\" -> \"+next, func(t *testing.T) {\n\t\t\terr := ValidateVersionUpgrade(current, next, \"\")\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expected %s -> %s to be valid upgrade, got error: %v\", current, next, err)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Verify reverse order is invalid\n\tfor i := len(versions) - 1; i > 0; i-- {\n\t\tcurrent := versions[i]\n\t\tprevious := versions[i-1]\n\t\tt.Run(current+\" -> \"+previous+\" (downgrade)\", func(t *testing.T) {\n\t\t\terr := ValidateVersionUpgrade(current, previous, \"\")\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Expected %s -> %s to be invalid downgrade\", current, previous)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  execution:\n    build:\n      context: .\n      dockerfile: ${CLIENT:-geth}/Dockerfile\n    restart: unless-stopped\n    ports:\n      - \"8545:8545\" # RPC\n      - \"8546:8546\" # websocket\n      - \"7301:6060\" # metrics\n      - \"30303:30303\" # P2P TCP\n      - \"30303:30303/udp\" # P2P UDP\n    command: [\"bash\", \"./execution-entrypoint\"]\n    volumes:\n      - ${HOST_DATA_DIR}:/data\n    env_file:\n      - ${NETWORK_ENV:-.env.mainnet} # Use .env.mainnet by default, override with .env.sepolia for testnet\n  node:\n    build:\n      context: .\n      dockerfile: ${CLIENT:-geth}/Dockerfile\n    restart: unless-stopped\n    depends_on:\n      - execution\n    ports:\n      - \"7545:8545\" # RPC\n      - \"9222:9222\" # P2P TCP\n      - \"9222:9222/udp\" # P2P UDP\n      - \"7300:7300\" # metrics\n      - \"6060:6060\" # pprof\n    command: [\"bash\", \"./op-node-entrypoint\"]\n    env_file:\n      - ${NETWORK_ENV:-.env.mainnet} # Use .env.mainnet by default, override with .env.sepolia for testnet\n"
  },
  {
    "path": "geth/Dockerfile",
    "content": "FROM golang:1.24 AS op\n\nRUN curl -sSfL 'https://just.systems/install.sh' | bash -s -- --to /usr/local/bin\n\nWORKDIR /app\n\nCOPY versions.env /tmp/versions.env\n\nRUN . /tmp/versions.env && git clone $OP_NODE_REPO --branch $OP_NODE_TAG --single-branch . && \\\n    git switch -c branch-$OP_NODE_TAG && \\\n    bash -c '[ \"$(git rev-parse HEAD)\" = \"$OP_NODE_COMMIT\" ]'\n\nRUN . /tmp/versions.env && cd op-node && \\\n    make VERSION=$OP_NODE_TAG op-node\n\nFROM golang:1.24 AS geth\n\nWORKDIR /app\n\nCOPY versions.env /tmp/versions.env\n\nRUN . /tmp/versions.env && git clone $OP_GETH_REPO --branch $OP_GETH_TAG --single-branch . && \\\n    git switch -c branch-$OP_GETH_TAG && \\\n    bash -c '[ \"$(git rev-parse HEAD)\" = \"$OP_GETH_COMMIT\" ]'\n\nRUN go run build/ci.go install -static ./cmd/geth\n\nFROM ubuntu:24.04\n\nRUN apt-get update && \\\n    apt-get install -y jq curl supervisor && \\\n    rm -rf /var/lib/apt/lists\nRUN mkdir -p /var/log/supervisor\n\nWORKDIR /app\n\nCOPY --from=op /app/op-node/bin/op-node ./\nCOPY --from=geth /app/build/bin/geth ./\nCOPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf\nCOPY geth/geth-entrypoint ./execution-entrypoint\nCOPY op-node-entrypoint .\n\nCMD [\"/usr/bin/supervisord\"]\n"
  },
  {
    "path": "geth/geth-entrypoint",
    "content": "#!/bin/bash\nset -eu\n\nVERBOSITY=${GETH_VERBOSITY:-3}\nGETH_DATA_DIR=${GETH_DATA_DIR:-/data}\nRPC_PORT=\"${RPC_PORT:-8545}\"\nWS_PORT=\"${WS_PORT:-8546}\"\nAUTHRPC_PORT=\"${AUTHRPC_PORT:-8551}\"\nMETRICS_PORT=\"${METRICS_PORT:-6060}\"\nHOST_IP=\"\" # put your external IP address here and open port 30303 to improve peer connectivity\nP2P_PORT=\"${P2P_PORT:-30303}\"\nDISCOVERY_PORT=\"${DISCOVERY_PORT:-30303}\"\nADDITIONAL_ARGS=\"\"\nOP_GETH_GCMODE=\"${OP_GETH_GCMODE:-full}\"\nOP_GETH_SYNCMODE=\"${OP_GETH_SYNCMODE:-full}\"\n\n# Add cache optimizations with defaults\nGETH_CACHE=\"${GETH_CACHE:-20480}\"\nGETH_CACHE_DATABASE=\"${GETH_CACHE_DATABASE:-20}\"\nGETH_CACHE_GC=\"${GETH_CACHE_GC:-12}\"\nGETH_CACHE_SNAPSHOT=\"${GETH_CACHE_SNAPSHOT:-24}\"\nGETH_CACHE_TRIE=\"${GETH_CACHE_TRIE:-44}\"\n\nif [[ -z \"$OP_NODE_NETWORK\" ]]; then\n    echo \"expected OP_NODE_NETWORK to be set\" 1>&2\n    exit 1\nfi\n\nmkdir -p $GETH_DATA_DIR\n\necho \"$OP_NODE_L2_ENGINE_AUTH_RAW\" > \"$OP_NODE_L2_ENGINE_AUTH\"\n\nif [ \"${OP_GETH_ETH_STATS+x}\" = x ]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --ethstats=$OP_GETH_ETH_STATS\"\nfi\n\nif [ \"${OP_GETH_ALLOW_UNPROTECTED_TXS+x}\" = x ]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --rpc.allow-unprotected-txs=$OP_GETH_ALLOW_UNPROTECTED_TXS\"\nfi\n\nif [ \"${OP_GETH_STATE_SCHEME+x}\" = x ]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --state.scheme=$OP_GETH_STATE_SCHEME\"\nfi\n\nif [ \"${OP_GETH_BOOTNODES+x}\" = x ]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --bootnodes=$OP_GETH_BOOTNODES\"\nfi\n\nif [ \"${HOST_IP:+x}\" = x ]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --nat=extip:$HOST_IP\"\nfi \n\nexec ./geth \\\n    --datadir=\"$GETH_DATA_DIR\" \\\n    --verbosity=\"$VERBOSITY\" \\\n    --http \\\n    --http.corsdomain=\"*\" \\\n    --http.vhosts=\"*\" \\\n    --http.addr=0.0.0.0 \\\n    --http.port=\"$RPC_PORT\" \\\n    --http.api=web3,debug,eth,net,engine \\\n    --authrpc.addr=0.0.0.0 \\\n    --authrpc.port=\"$AUTHRPC_PORT\" \\\n    --authrpc.vhosts=\"*\" \\\n    --authrpc.jwtsecret=\"$OP_NODE_L2_ENGINE_AUTH\" \\\n    --ws \\\n    --ws.addr=0.0.0.0 \\\n    --ws.port=\"$WS_PORT\" \\\n    --ws.origins=\"*\" \\\n    --ws.api=debug,eth,net,engine \\\n    --metrics \\\n    --metrics.addr=0.0.0.0 \\\n    --metrics.port=\"$METRICS_PORT\" \\\n    --syncmode=\"$OP_GETH_SYNCMODE\" \\\n    --gcmode=\"$OP_GETH_GCMODE\" \\\n    --maxpeers=100 \\\n    --rollup.sequencerhttp=\"$OP_GETH_SEQUENCER_HTTP\" \\\n    --rollup.halt=major \\\n    --op-network=\"$OP_NODE_NETWORK\" \\\n    --discovery.port=\"$DISCOVERY_PORT\" \\\n    --port=\"$P2P_PORT\" \\\n    --rollup.disabletxpoolgossip=true \\\n    --cache=\"$GETH_CACHE\" \\\n    --cache.database=\"$GETH_CACHE_DATABASE\" \\\n    --cache.gc=\"$GETH_CACHE_GC\" \\\n    --cache.snapshot=\"$GETH_CACHE_SNAPSHOT\" \\\n    --cache.trie=\"$GETH_CACHE_TRIE\" \\\n    $ADDITIONAL_ARGS # intentionally unquoted\n"
  },
  {
    "path": "go.mod",
    "content": "module dependency_updater\n\ngo 1.24.3\n"
  },
  {
    "path": "nethermind/Dockerfile",
    "content": "FROM golang:1.24 AS op\n\nRUN curl -sSfL 'https://just.systems/install.sh' | bash -s -- --to /usr/local/bin\n\nWORKDIR /app\n\nCOPY versions.env /tmp/versions.env\n\nRUN . /tmp/versions.env && git clone $OP_NODE_REPO --branch $OP_NODE_TAG --single-branch . && \\\n    git switch -c branch-$OP_NODE_TAG && \\\n    bash -c '[ \"$(git rev-parse HEAD)\" = \"$OP_NODE_COMMIT\" ]'\n\nRUN . /tmp/versions.env && cd op-node && \\\n    just VERSION=$OP_NODE_TAG op-node\n\nFROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build\n\nARG BUILD_CONFIG=release\nARG TARGETARCH\n\nWORKDIR /app\n\nCOPY versions.env /tmp/versions.env\n\nRUN . /tmp/versions.env && git clone $NETHERMIND_REPO --branch $NETHERMIND_TAG --single-branch . && \\\n    git switch -c $NETHERMIND_TAG && \\\n    bash -c '[ \"$(git rev-parse HEAD)\" = \"$NETHERMIND_COMMIT\" ]'\n    \nRUN TARGETARCH=${TARGETARCH#linux/} && \\\n    arch=$([ \"$TARGETARCH\" = \"amd64\" ] && echo \"x64\" || echo \"$TARGETARCH\") && \\\n    echo \"Using architecture: $arch\" && \\\n    dotnet publish src/Nethermind/Nethermind.Runner -c $BUILD_CONFIG -a $arch -o /publish --sc false\n\nFROM mcr.microsoft.com/dotnet/aspnet:10.0-noble\n\nRUN apt-get update && \\\n    apt-get install -y jq curl supervisor && \\\n    rm -rf /var/lib/apt/lists/*\n\nRUN mkdir -p /var/log/supervisor\n\nWORKDIR /app\n\nCOPY --from=build /publish ./\nCOPY --from=op /app/op-node/bin/op-node ./\nCOPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf\nCOPY nethermind/nethermind-entrypoint ./execution-entrypoint\nCOPY op-node-entrypoint .\n\nCMD [\"/usr/bin/supervisord\"]\n"
  },
  {
    "path": "nethermind/nethermind-entrypoint",
    "content": "#!/bin/bash\nset -eu\n\n# Default configurations\nNETHERMIND_DATA_DIR=${NETHERMIND_DATA_DIR:-/data}\nNETHERMIND_LOG_LEVEL=${NETHERMIND_LOG_LEVEL:-Info}\n\nRPC_PORT=\"${RPC_PORT:-8545}\"\nWS_PORT=\"${WS_PORT:-8546}\"\nAUTHRPC_PORT=\"${AUTHRPC_PORT:-8551}\"\nMETRICS_PORT=\"${METRICS_PORT:-6060}\"\nDISCOVERY_PORT=\"${DISCOVERY_PORT:-30303}\"\nP2P_PORT=\"${P2P_PORT:-30303}\"\nADDITIONAL_ARGS=\"\"\n\n# Check if required variables are set\nif [[ -z \"$OP_NODE_NETWORK\" ]]; then\n    echo \"Expected OP_NODE_NETWORK to be set\" 1>&2\n    exit 1\nfi\n\n# Create necessary directories\nmkdir -p \"$NETHERMIND_DATA_DIR\"\n\n# Write the JWT secret\nif [[ -z \"$OP_NODE_L2_ENGINE_AUTH_RAW\" ]]; then\n    echo \"Expected OP_NODE_L2_ENGINE_AUTH_RAW to be set\" 1>&2\n    exit 1\nfi\necho \"$OP_NODE_L2_ENGINE_AUTH_RAW\" > \"$OP_NODE_L2_ENGINE_AUTH\"\n\n# Additional arguments based on environment variables\nif [[ -n \"${OP_NETHERMIND_BOOTNODES:-}\" ]]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --Network.Bootnodes=$OP_NETHERMIND_BOOTNODES\"\nfi\n\nif [[ -n \"${OP_NETHERMIND_ETHSTATS_ENABLED:-}\" ]]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --EthStats.Enabled=$OP_NETHERMIND_ETHSTATS_ENABLED\"\nfi\n\nif [[ -n \"${OP_NETHERMIND_ETHSTATS_ENDPOINT:-}\" ]]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --EthStats.NodeName=${OP_NETHERMIND_ETHSTATS_NODE_NAME:-NethermindNode} --EthStats.Endpoint=$OP_NETHERMIND_ETHSTATS_ENDPOINT\"\nfi\n\n# Execute Nethermind\nexec ./nethermind \\\n    --config=\"$OP_NODE_NETWORK\" \\\n    --datadir=\"$NETHERMIND_DATA_DIR\" \\\n    --Optimism.SequencerUrl=$OP_SEQUENCER_HTTP \\\n    --log=\"$NETHERMIND_LOG_LEVEL\" \\\n    --JsonRpc.Enabled=true \\\n    --JsonRpc.Host=0.0.0.0 \\\n    --JsonRpc.WebSocketsPort=\"$WS_PORT\" \\\n    --JsonRpc.Port=\"$RPC_PORT\" \\\n    --JsonRpc.JwtSecretFile=\"$OP_NODE_L2_ENGINE_AUTH\" \\\n    --JsonRpc.EngineHost=0.0.0.0 \\\n    --JsonRpc.EnginePort=\"$AUTHRPC_PORT\" \\\n    --HealthChecks.Enabled=true \\\n    --Metrics.Enabled=true \\\n    --Metrics.ExposePort=\"$METRICS_PORT\" \\\n    --Network.P2PPort=\"$P2P_PORT\" \\\n    --Network.DiscoveryPort=\"$DISCOVERY_PORT\" \\\n    $ADDITIONAL_ARGS\n"
  },
  {
    "path": "op-node-entrypoint",
    "content": "#!/bin/bash\nset -eu\n\nget_public_ip() {\n  # Define a list of HTTP-based providers\n  local PROVIDERS=(\n    \"http://ifconfig.me\"\n    \"http://api.ipify.org\"\n    \"http://ipecho.net/plain\"\n    \"http://v4.ident.me\"\n  )\n  # Iterate through the providers until an IP is found or the list is exhausted\n  for provider in \"${PROVIDERS[@]}\"; do\n    local IP\n    IP=$(curl -s --max-time 10 --connect-timeout 5 \"$provider\")\n    # Check if IP contains a valid format (simple regex for an IPv4 address)\n    if [[ $IP =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n      echo \"$IP\"\n      return 0\n    fi\n  done\n  return 1\n}\n\nif [[ -z \"$OP_NODE_NETWORK\" && -z \"$OP_NODE_ROLLUP_CONFIG\" ]]; then\n  echo \"expected OP_NODE_NETWORK to be set\" 1>&2\n  exit 1\nfi\n\n# wait until local execution client comes up (authed so will return 401 without token)\nuntil [ \"$(curl -s --max-time 10 --connect-timeout 5 -w '%{http_code}' -o /dev/null \"${OP_NODE_L2_ENGINE_RPC/ws/http}\")\" -eq 401 ]; do\n  echo \"waiting for execution client to be ready\"\n  sleep 5\ndone\n\n# public-facing P2P node, advertise public IP address\nif PUBLIC_IP=$(get_public_ip); then\n  echo \"fetched public IP is: $PUBLIC_IP\"\nelse\n  echo \"Could not retrieve public IP.\"\n  exit 8\nfi\nexport OP_NODE_P2P_ADVERTISE_IP=$PUBLIC_IP\n\necho \"$OP_NODE_L2_ENGINE_AUTH_RAW\" > \"$OP_NODE_L2_ENGINE_AUTH\"\n\nexec ./op-node\n"
  },
  {
    "path": "reth/Dockerfile",
    "content": "FROM golang:1.24 AS op\n\nRUN curl -sSfL 'https://just.systems/install.sh' | bash -s -- --to /usr/local/bin\n\nWORKDIR /app\n\nCOPY versions.env /tmp/versions.env\n\nRUN . /tmp/versions.env && git clone $OP_NODE_REPO --branch $OP_NODE_TAG --single-branch . && \\\n    git switch -c branch-$OP_NODE_TAG && \\\n    bash -c '[ \"$(git rev-parse HEAD)\" = \"$OP_NODE_COMMIT\" ]'\n\nRUN . /tmp/versions.env && cd op-node && \\\n    make VERSION=$OP_NODE_TAG op-node\n\nFROM rust:1.88 AS reth-base\n\nWORKDIR /app\n\nCOPY versions.env /tmp/versions.env\n\nRUN apt-get update && apt-get -y upgrade && \\\n    apt-get install -y git libclang-dev pkg-config curl build-essential && \\\n    rm -rf /var/lib/apt/lists/*\n\nRUN . /tmp/versions.env && git clone $BASE_RETH_NODE_REPO . && \\\n    git checkout tags/$BASE_RETH_NODE_TAG && \\\n    bash -c '[ \"$(git rev-parse HEAD)\" = \"$BASE_RETH_NODE_COMMIT\" ]' || (echo \"Commit hash verification failed\" && exit 1)\n\nRUN cargo build --bin base-reth-node --profile maxperf\n\nFROM ubuntu:24.04\n\nRUN apt-get update && \\\n    apt-get install -y jq curl supervisor && \\\n    rm -rf /var/lib/apt/lists/*\nRUN mkdir -p /var/log/supervisor\n\nWORKDIR /app\n\nCOPY --from=op /app/op-node/bin/op-node ./\nCOPY --from=reth-base /app/target/maxperf/base-reth-node ./\nCOPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf\nCOPY ./reth/reth-entrypoint ./execution-entrypoint\nCOPY op-node-entrypoint .\n\nCMD [\"/usr/bin/supervisord\"]\n"
  },
  {
    "path": "reth/README.md",
    "content": "# Running a Reth Node\n\nThis is an implementation of the Reth node setup that supports Flashblocks mode based on configuration.\n\n## Setup\n\n- See hardware requirements mentioned in the master README\n- For Flashblocks mode: Access to a Flashblocks websocket endpoint (for `RETH_FB_WEBSOCKET_URL`)\n  - We provide public websocket endpoints for mainnet and devnet, included in `.env.mainnet` and `.env.sepolia`\n\n## Node Type Selection\n\nThe node determines its mode based on the presence of the `RETH_FB_WEBSOCKET_URL` environment variable:\n\n- **Vanilla Mode** (default): When no `RETH_FB_WEBSOCKET_URL` is provided.\n- **Flashblocks Mode**: When `RETH_FB_WEBSOCKET_URL` is provided.\n\n## Running the Node\n\nThe node follows the standard `docker-compose` workflow in the master README.\n\n```bash\n# To run Reth node with Flashblocks support, set RETH_FB_WEBSOCKET_URL in your .env file\nCLIENT=reth docker-compose up\n```\n\n## Testing Flashblocks RPC Methods\n\nWhen running in Flashblocks mode (with `RETH_FB_WEBSOCKET_URL` configured), you can query a pending block using the Flashblocks RPC:\n\n```bash\ncurl -X POST \\\n  --data '{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBlockByNumber\",\"params\":[\"pending\", false],\"id\":1}' \\\n  http://localhost:8545\n```\n\n## Additional RPC Methods\n\nFor a complete list of supported RPC methods, refer to:\n\n- [Standard Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/)\n- [Flashblocks RPC Methods](https://docs.base.org/chain/flashblocks#rpc-api) (Flashblocks mode only)\n"
  },
  {
    "path": "reth/reth-entrypoint",
    "content": "#!/bin/bash\nset -eu\n\nIPC_PATH=\"/data/reth.ipc\"\nRETH_DATA_DIR=/data\nRPC_PORT=\"${RPC_PORT:-8545}\"\nWS_PORT=\"${WS_PORT:-8546}\"\nAUTHRPC_PORT=\"${AUTHRPC_PORT:-8551}\"\nMETRICS_PORT=\"${METRICS_PORT:-6060}\"\nDISCOVERY_PORT=\"${DISCOVERY_PORT:-30303}\"\nP2P_PORT=\"${P2P_PORT:-30303}\"\nADDITIONAL_ARGS=\"\"\nBINARY=\"./base-reth-node\"\nRETH_HISTORICAL_PROOFS=\"${RETH_HISTORICAL_PROOFS:-false}\"\nRETH_HISTORICAL_PROOFS_STORAGE_PATH=\"${RETH_HISTORICAL_PROOFS_STORAGE_PATH:-}\"\nLOG_LEVEL=\"${LOG_LEVEL:-info}\"\n\nif [[ -z \"${RETH_CHAIN:-}\" ]]; then\n    echo \"expected RETH_CHAIN to be set\" 1>&2\n    exit 1\nfi\n\n# Enable Flashblocks support if websocket URL is provided\nif [[ -n \"${RETH_FB_WEBSOCKET_URL:-}\" ]]; then\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --websocket-url=$RETH_FB_WEBSOCKET_URL\"\n    echo \"Enabling Flashblocks support with endpoint: $RETH_FB_WEBSOCKET_URL\"\nelse\n    echo \"Running in vanilla node mode (no Flashblocks URL provided)\"\nfi\n\ncase \"$LOG_LEVEL\" in\n    \"error\")\n        LOG_LEVEL=\"v\"\n        ;;\n    \"warn\")\n        LOG_LEVEL=\"vv\"\n        ;;\n    \"info\"|*)\n        LOG_LEVEL=\"vvv\"\n        ;;\n    \"debug\")\n        LOG_LEVEL=\"vvvv\"\n        ;;\n    \"trace\")\n        LOG_LEVEL=\"vvvvv\"\n        ;;\nesac\n\n# Add pruning for base\nif [[ \"${RETH_PRUNING_ARGS+x}\" = x ]]; then\n    echo \"Adding pruning arguments: $RETH_PRUNING_ARGS\"\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS $RETH_PRUNING_ARGS\"\nfi\n\nif [[ \"$RETH_HISTORICAL_PROOFS\" == \"true\" && -n \"$RETH_HISTORICAL_PROOFS_STORAGE_PATH\" ]]; then\n    # reth doesn't like starting an old database in RO mode, so we have to start the reth node, wait for it to start up, then shut it down first\n    \"$BINARY\" node \\\n        -$LOG_LEVEL \\\n        --datadir=\"$RETH_DATA_DIR\" \\\n        --log.stdout.format json \\\n        --http \\\n        --http.addr=127.0.0.1 \\\n        --http.port=\"$RPC_PORT\" \\\n        --http.api=eth \\\n        --chain \"$RETH_CHAIN\" &\n\n    PID=$!\n\n    MAX_WAIT=$((60 * 60 * 6)) # 6 hours (static file manager init is slow)\n\n    # wait for json-rpc to return a block number greater than 0 (synced beyond genesis)\n    while true; do\n        RESPONSE=$(curl -s -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBlockByNumber\",\"params\":[\"latest\", false],\"id\":1}' http://127.0.0.1:\"$RPC_PORT\" 2>/dev/null || true)\n        \n        if echo \"$RESPONSE\" | grep -q '\"number\":\"0x0\"'; then\n            echo \"waiting for reth node to sync beyond genesis block\"\n        elif echo \"$RESPONSE\" | grep -q '\"result\"'; then\n            # curl succeeded and returned a valid result with block number != 0x0\n            break\n        else\n            echo \"waiting for reth node to start up\"\n        fi\n        \n        sleep 1\n        MAX_WAIT=$((MAX_WAIT - 1))\n        if [ \"$MAX_WAIT\" -eq 0 ]; then\n            echo \"timed out waiting for reth node to start up\"\n            kill \"$PID\"\n            exit 1\n        fi\n    done\n\n    # shut down gracefully\n    kill \"$PID\"\n\n    (wait \"$PID\" && echo \"reth node initialized\") || echo \"warning: reth node exited with code $?\"\n\n    ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS --proofs-history --proofs-history.storage-path=$RETH_HISTORICAL_PROOFS_STORAGE_PATH\"\n\n    # in this case, we need to run the init script first (idempotent)\n    \"$BINARY\" proofs init \\\n        -$LOG_LEVEL \\\n        --log.stdout.format json \\\n        --chain \"$RETH_CHAIN\" \\\n        --datadir=\"$RETH_DATA_DIR\" \\\n        --proofs-history.storage-path=$RETH_HISTORICAL_PROOFS_STORAGE_PATH\nfi\n\nmkdir -p \"$RETH_DATA_DIR\"\necho \"Starting reth with additional args: $ADDITIONAL_ARGS\"\necho \"$OP_NODE_L2_ENGINE_AUTH_RAW\" > \"$OP_NODE_L2_ENGINE_AUTH\"\n\nexec \"$BINARY\" node \\\n  -$LOG_LEVEL \\\n  --datadir=\"$RETH_DATA_DIR\" \\\n  --log.stdout.format json \\\n  --ws \\\n  --ws.origins=\"*\" \\\n  --ws.addr=0.0.0.0 \\\n  --ws.port=\"$WS_PORT\" \\\n  --ws.api=web3,debug,eth,net,txpool \\\n  --http \\\n  --http.corsdomain=\"*\" \\\n  --http.addr=0.0.0.0 \\\n  --http.port=\"$RPC_PORT\" \\\n  --http.api=web3,debug,eth,net,txpool,miner \\\n  --ipcpath=\"$IPC_PATH\" \\\n  --authrpc.addr=0.0.0.0 \\\n  --authrpc.port=\"$AUTHRPC_PORT\" \\\n  --authrpc.jwtsecret=\"$OP_NODE_L2_ENGINE_AUTH\" \\\n  --metrics=0.0.0.0:\"$METRICS_PORT\" \\\n  --max-outbound-peers=100 \\\n  --chain \"$RETH_CHAIN\" \\\n  --rollup.sequencer-http=\"$RETH_SEQUENCER_HTTP\" \\\n  --rollup.disable-tx-pool-gossip \\\n  --discovery.port=\"$DISCOVERY_PORT\" \\\n  --port=\"$P2P_PORT\" \\\n  $ADDITIONAL_ARGS\n"
  },
  {
    "path": "supervisord.conf",
    "content": "[supervisord]\nnodaemon=true\nlogfile=/dev/null\nlogfile_maxbytes=0\n\n[program:op-node]\ncommand=/app/op-node-entrypoint\nstdout_logfile=/dev/fd/1\nstdout_logfile_maxbytes=0\nredirect_stderr=true\nstopwaitsecs=300\n\n[program:op-execution]\ncommand=/app/execution-entrypoint\nstdout_logfile=/dev/fd/1\nstdout_logfile_maxbytes=0\nredirect_stderr=true\nstopwaitsecs=300\n"
  },
  {
    "path": "versions.env",
    "content": "export BASE_RETH_NODE_COMMIT=6e54e8cbcbd27cb86bde54d3a5b0e9b4e9ea960a\nexport BASE_RETH_NODE_REPO=https://github.com/base/base.git\nexport BASE_RETH_NODE_TAG=v0.5.1\nexport NETHERMIND_COMMIT=31cb81b7328026791cdfaccd9db230c82f1db02d\nexport NETHERMIND_REPO=https://github.com/NethermindEth/nethermind.git\nexport NETHERMIND_TAG=1.36.0\nexport OP_GETH_COMMIT=878a554cc84cead647d20ba366043c6fd41ebf1c\nexport OP_GETH_REPO=https://github.com/ethereum-optimism/op-geth.git\nexport OP_GETH_TAG=v1.101609.1\nexport OP_NODE_COMMIT=3019251e80aa248e91743addd3e833190acb26f1\nexport OP_NODE_REPO=https://github.com/ethereum-optimism/optimism.git\nexport OP_NODE_TAG=op-node/v1.16.7"
  },
  {
    "path": "versions.json",
    "content": "{\n\t  \"base_reth_node\": {\n\t  \t  \"tag\": \"v0.5.1\",\n\t  \t  \"commit\": \"6e54e8cbcbd27cb86bde54d3a5b0e9b4e9ea960a\",\n\t  \t  \"owner\": \"base\",\n\t  \t  \"repo\": \"base\",\n\t  \t  \"tracking\": \"release\"\n\t  },\n\t  \"nethermind\": {\n\t  \t  \"tag\": \"1.36.0\",\n\t  \t  \"commit\": \"31cb81b7328026791cdfaccd9db230c82f1db02d\",\n\t  \t  \"owner\": \"NethermindEth\",\n\t  \t  \"repo\": \"nethermind\",\n\t  \t  \"tracking\": \"release\"\n\t  },\n\t  \"op_geth\": {\n\t  \t  \"tag\": \"v1.101609.1\",\n\t  \t  \"commit\": \"878a554cc84cead647d20ba366043c6fd41ebf1c\",\n\t  \t  \"owner\": \"ethereum-optimism\",\n\t  \t  \"repo\": \"op-geth\",\n\t  \t  \"tracking\": \"release\"\n\t  },\n\t  \"op_node\": {\n\t  \t  \"tag\": \"op-node/v1.16.7\",\n\t  \t  \"commit\": \"3019251e80aa248e91743addd3e833190acb26f1\",\n\t  \t  \"tagPrefix\": \"op-node\",\n\t  \t  \"owner\": \"ethereum-optimism\",\n\t  \t  \"repo\": \"optimism\",\n\t  \t  \"tracking\": \"release\"\n\t  }\n}"
  }
]