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