├── grafana ├── grafana.ini ├── dashboards.yml ├── datasource.yml └── dashboards │ ├── simnet_dash.json │ └── local_cluster_dash.json ├── jwt └── jwt.hex ├── relay └── sample ├── .charon └── sample ├── DVCluster.png ├── example-infura-details.png ├── vouch ├── Dockerfile ├── vouch.yml └── run.sh ├── nimbus ├── Dockerfile └── run.sh ├── .gitignore ├── teku └── teku-config.yaml ├── .github ├── workflows │ ├── label-issues.yml │ ├── add_issue_to_project.yml │ └── dispath-update.yml ├── renovate.json └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_ticket.md ├── lighthouse └── run.sh ├── prometheus └── prometheus.yml ├── README.md ├── .env.sample ├── docker-compose.override.yml.sample └── docker-compose.yml /grafana/grafana.ini: -------------------------------------------------------------------------------- 1 | [auth.anonymous] 2 | enabled = true 3 | org_role = Admin -------------------------------------------------------------------------------- /jwt/jwt.hex: -------------------------------------------------------------------------------- 1 | 7074a5bf6bd6dae368fa598249d57edfcbccc67a1205b2c8d5d2fe7b800663aa -------------------------------------------------------------------------------- /relay/sample: -------------------------------------------------------------------------------- 1 | This is a placeholder file to get relay directory included in git. -------------------------------------------------------------------------------- /.charon/sample: -------------------------------------------------------------------------------- 1 | This is a placeholder file to get .charon directory included in git. -------------------------------------------------------------------------------- /DVCluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObolNetwork/charon-distributed-validator-cluster/HEAD/DVCluster.png -------------------------------------------------------------------------------- /example-infura-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObolNetwork/charon-distributed-validator-cluster/HEAD/example-infura-details.png -------------------------------------------------------------------------------- /grafana/dashboards.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: dashboards 5 | type: file 6 | updateIntervalSeconds: 30 7 | options: 8 | path: /etc/dashboards 9 | -------------------------------------------------------------------------------- /vouch/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM wealdtech/ethdo:1.35.2 as ethdo 2 | 3 | FROM attestant/vouch:1.8.0 4 | 5 | COPY --from=ethdo /app/ethdo /app/ethdo 6 | 7 | RUN apt-get update && apt-get install -y curl jq wget 8 | 9 | ENTRYPOINT ["/opt/vouch/run.sh"] -------------------------------------------------------------------------------- /nimbus/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM statusim/nimbus-eth2:multiarch-v25.9.2 as nimbusbn 2 | 3 | FROM statusim/nimbus-validator-client:multiarch-v25.9.2 4 | 5 | COPY --from=nimbusbn /home/user/nimbus_beacon_node /home/user/nimbus_beacon_node 6 | 7 | ENTRYPOINT ["/home/user/data/run.sh"] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | charon.yml 2 | charon 3 | .charon/cluster/ 4 | manifest.json 5 | cluster_definition.json 6 | cluster_lock.json 7 | p2pkey 8 | keystore* 9 | prater 10 | .env 11 | .data 12 | beaconnode.env.old 13 | split_keys/ 14 | relay/charon-enr-private-key 15 | .idea/ 16 | data/ 17 | nethermind/nethermind_db/ 18 | docker-compose.override.yml 19 | nimbus/node*/ -------------------------------------------------------------------------------- /teku/teku-config.yaml: -------------------------------------------------------------------------------- 1 | metrics-enabled: true 2 | metrics-host-allowlist: "*" 3 | metrics-interface: "0.0.0.0" 4 | metrics-port: "8008" 5 | validators-keystore-locking-enabled: false 6 | network: auto 7 | validator-keys: "/opt/charon/validator_keys:/opt/charon/validator_keys" 8 | validators-proposer-default-fee-recipient: "0x0000000000000000000000000000000000000000" 9 | Xobol-dvt-integration-enabled: true 10 | -------------------------------------------------------------------------------- /.github/workflows/label-issues.yml: -------------------------------------------------------------------------------- 1 | name: Label issues 2 | on: 3 | issues: 4 | types: 5 | - reopened 6 | - opened 7 | jobs: 8 | label_issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | steps: 13 | - uses: actions/github-script@v8 14 | with: 15 | script: | 16 | github.rest.issues.addLabels({ 17 | issue_number: context.issue.number, 18 | owner: context.repo.owner, 19 | repo: context.repo.repo, 20 | labels: ["protocol"] 21 | }) 22 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "enabledManagers": [ 7 | "github-actions" 8 | ], 9 | "packageRules": [ 10 | { 11 | "matchManagers": [ 12 | "github-actions" 13 | ], 14 | "matchDepTypes": [ 15 | "github-actions" 16 | ], 17 | "matchFileNames": [ 18 | ".github/workflows/**" 19 | ], 20 | "schedule": [ 21 | "every weekend" 22 | ], 23 | "labels": [ 24 | "renovate/github-actions" 25 | ], 26 | "groupName": "GitHub Actions updates" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/add_issue_to_project.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow adding every issue in this repo to the new Project Management Board 2 | name: Add Issue To Project 3 | 4 | # Controls when the workflow will run - new issues 5 | on: 6 | issues: 7 | types: 8 | - opened 9 | 10 | # This workflow contains a single job called "build" 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | # Steps 15 | steps: 16 | - name: Add Issue To Project 17 | uses: actions/add-to-project@v1.0.2 18 | with: 19 | # URL of the project to add issues to 20 | project-url: https://github.com/orgs/ObolNetwork/projects/7 21 | # A GitHub personal access token with write access to the project, need org admin's token with repo and project permissions, need to store the token outside the script if public 22 | github-token: ${{ secrets.GH_ORG_ADMIN_SECRET }} 23 | -------------------------------------------------------------------------------- /lighthouse/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | apt-get update && apt-get install -y curl jq wget 4 | 5 | while ! curl "${LIGHTHOUSE_BEACON_NODE_ADDRESS}/eth/v1/node/health" 2>/dev/null; do 6 | echo "Waiting for ${LIGHTHOUSE_BEACON_NODE_ADDRESS} to become available..." 7 | sleep 5 8 | done 9 | 10 | # Refer: https://lighthouse-book.sigmaprime.io/advanced-datadir.html 11 | # Running a lighthouse VC involves two steps which needs to run in order: 12 | # 1. Loading the validator keys 13 | # 2. Actually running the VC 14 | 15 | for f in /opt/charon/keys/keystore-*.json; do 16 | echo "Importing key ${f}" 17 | lighthouse --network "${ETH2_NETWORK}" account validator import \ 18 | --reuse-password \ 19 | --keystore "${f}" \ 20 | --password-file "${f//json/txt}" 21 | done 22 | 23 | echo "Starting lighthouse validator client for ${NODE}" 24 | exec lighthouse --network "${ETH2_NETWORK}" validator \ 25 | --beacon-nodes ${LIGHTHOUSE_BEACON_NODE_ADDRESS} \ 26 | --suggested-fee-recipient "0x0000000000000000000000000000000000000000" \ 27 | --metrics \ 28 | --metrics-address "0.0.0.0" \ 29 | --metrics-allow-origin "*" \ 30 | --metrics-port "5064" \ 31 | --use-long-timeouts \ 32 | --distributed 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug report" 3 | about: Report a bug or problem with running this repo 4 | labels: Bug 5 | --- 6 | 14 | 15 | # 🐞 Bug Report 16 | 17 | ### Description 18 | 19 | A clear and concise description of the problem... 20 | 21 | ## 🔬 Minimal Reproduction 22 | 23 | 26 | 27 | ## 🔥 Error 28 | 29 |

30 | 
31 | 
32 | 
33 | 
34 | 35 | 36 | ## 🌍 Your Environment 37 | 38 | **Operating System:** 39 | 40 |
41 |   
42 | 
43 |   
44 | 
45 | 46 | **What version of Charon are you running? (Which release)** 47 | 48 |
49 |   
50 | 
51 |   
52 | 
53 | 54 | **Anything else relevant (validator index / public key)?** 55 | 56 | -------------------------------------------------------------------------------- /nimbus/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Cleanup nimbus directories if they already exist. 4 | rm -rf /home/user/data/${NODE} 5 | 6 | # Refer: https://nimbus.guide/keys.html 7 | # Running a nimbus VC involves two steps which need to run in order: 8 | # 1. Importing the validator keys 9 | # 2. And then actually running the VC 10 | tmpkeys="/home/validator_keys/tmpkeys" 11 | mkdir -p ${tmpkeys} 12 | 13 | for f in /home/validator_keys/keystore-*.json; do 14 | echo "Importing key ${f}" 15 | 16 | # Read password from keystore-*.txt into $password variable. 17 | password=$(<"${f//json/txt}") 18 | 19 | # Copy keystore file to tmpkeys/ directory. 20 | cp "${f}" "${tmpkeys}" 21 | 22 | # Import keystore with the password. 23 | echo "$password" | \ 24 | /home/user/nimbus_beacon_node deposits import \ 25 | --data-dir=/home/user/data/${NODE} \ 26 | /home/validator_keys/tmpkeys 27 | 28 | # Delete tmpkeys/keystore-*.json file that was copied before. 29 | filename="$(basename ${f})" 30 | rm "${tmpkeys}/${filename}" 31 | done 32 | 33 | # Delete the tmpkeys/ directory since it's no longer needed. 34 | rm -r ${tmpkeys} 35 | 36 | echo "Imported all keys" 37 | 38 | # Now run nimbus VC 39 | exec /home/user/nimbus_validator_client \ 40 | --data-dir=/home/user/data/"${NODE}" \ 41 | --beacon-node="http://$NODE:3600" \ 42 | --doppelganger-detection=false \ 43 | --metrics \ 44 | --metrics-address=0.0.0.0 \ 45 | --distributed 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_ticket.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature or Improvement Ticket" 3 | about: Create a new feature or suggest an improvement 4 | labels: Enhancement 5 | --- 6 | 7 | # 🎯 Problem to be solved 8 | 9 | 10 | 11 | # 🛠️ Proposed solution 12 | 13 | 14 | 15 | # 🧪 Tests 16 | 17 | 18 | 19 | - [ ] Works in local docker-compose 20 | - [ ] Has a attested on a testnet at least once 21 | 22 | # 👐 Additional acceptance criteria 23 | 24 | 25 | 26 | # ❌ Out of Scope 27 | 28 | 29 | 30 | 37 | -------------------------------------------------------------------------------- /.github/workflows/dispath-update.yml: -------------------------------------------------------------------------------- 1 | name: Dispatch Update Version 2 | 3 | on: 4 | repository_dispatch: 5 | types: [update-version] 6 | 7 | jobs: 8 | update-version: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout this repository 12 | uses: actions/checkout@v5 13 | 14 | - name: Extract tag name 15 | run: echo "TAG_NAME=${{ github.event.client_payload.tag }}" >> $GITHUB_ENV 16 | 17 | - name: Update version in Ansible configuration 18 | run: | 19 | TAG_NAME="$(echo "${TAG_NAME}" | sed 's/^v//')" 20 | 21 | sed -i -E "s/# Charon docker container image version, e\.g\. \`v[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9\-]*\`/# Charon docker container image version, e\.g\. \`v${TAG_NAME}\`/" .env.sample 22 | sed -i -E 's|(image: obolnetwork/charon:\$\{CHARON_VERSION:-)v\.?[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9\-]*}|\1v'"${TAG_NAME}"'}|' docker-compose.yml 23 | 24 | - name: Create Pull Request 25 | uses: peter-evans/create-pull-request@v7 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | base: main 29 | branch: update-version-${{ env.TAG_NAME }} 30 | title: "Update version to ${{ env.TAG_NAME }}" 31 | body: "Automatically generated PR to update version to ${{ env.TAG_NAME }}" 32 | commit-message: "Update version to ${{ env.TAG_NAME }}" 33 | author-name: "obol-platform" 34 | author-email: "platform@obol.tech" 35 | -------------------------------------------------------------------------------- /grafana/datasource.yml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: Prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # whats available in the database 11 | datasources: 12 | # name of the datasource. Required 13 | - name: Prometheus 14 | # datasource type. Required 15 | type: prometheus 16 | # org id. will default to orgId 1 if not specified 17 | orgId: 1 18 | # url 19 | url: http://prometheus:9090 20 | # database password, if used 21 | password: 22 | # database user, if used 23 | user: 24 | # database name, if used 25 | database: 26 | # enable/disable basic auth 27 | basicAuth: false 28 | # enable/disable with credentials headers 29 | withCredentials: 30 | # mark as default datasource. Max one per org 31 | isDefault: true 32 | # fields that will be converted to json and stored in json_data 33 | jsonData: 34 | graphiteVersion: "1.1" 35 | tlsAuth: false 36 | tlsAuthWithCACert: false 37 | # json object of data that will be encrypted. 38 | secureJsonData: 39 | tlsCACert: "..." 40 | tlsClientCert: "..." 41 | tlsClientKey: "..." 42 | version: 1 43 | # allow users to edit datasources from the UI. 44 | editable: true -------------------------------------------------------------------------------- /vouch/vouch.yml: -------------------------------------------------------------------------------- 1 | # This is a sample config to run vouch VC. It is used by the run.sh script 2 | # to generate a custom config for each vouch VC connected to a charon node. 3 | # Refer: https://github.com/attestantio/vouch/blob/master/docs/configuration.md. 4 | 5 | # The wallet account manager obtains account information from local wallets, and signs locally. 6 | # It supports wallets created by ethdo. 7 | accountmanager: 8 | wallet: 9 | locations: /opt/vouch/keys 10 | accounts: validators 11 | passphrases: secret 12 | 13 | # metrics is the module that logs metrics, in this case using prometheus. Note that vouch doesn't emit metrics if 14 | # the following block is not provided. 15 | metrics: 16 | prometheus: 17 | # listen-address is the address on which prometheus listens for metrics requests. 18 | listen-address: 0.0.0.0:8081 19 | 20 | # Allow sufficient time (10s) to block while fetching duties for DVT. 21 | strategies: 22 | beaconblockproposal: 23 | timeout: 10s 24 | blindedbeaconblockproposal: 25 | timeout: 10s 26 | attestationdata: 27 | timeout: 10s 28 | aggregateattestation: 29 | timeout: 10s 30 | synccommitteecontribution: 31 | timeout: 10s 32 | 33 | # blockrelay provides information about working with local execution clients and remote relays for block proposals. 34 | # fallback-fee-recipient is required field for vouch if no execution configuration is provided. 35 | blockrelay: 36 | fallback-fee-recipient: '0x0000000000000000000000000000000000000001' 37 | -------------------------------------------------------------------------------- /vouch/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Running vouch VC is split into three steps: 4 | # 1. Converting keys into a format which vouch understands. This is what ethdo does. 5 | # 2. Creating configuration for vouch (vouch.yml). 6 | # 3. Actually running the vouch validator client. 7 | 8 | BASE_DIR="/opt/vouch" 9 | KEYS_DIR="/opt/vouch/keys" 10 | ACCOUNT_PASSPHRASE="secret" # Hardcoded ethdo account passphrase 11 | 12 | # Create an ethdo wallet within the keys folder. 13 | wallet="validators" 14 | /app/ethdo --base-dir="${KEYS_DIR}" wallet create --wallet ${wallet} 15 | 16 | # Import keys into the ethdo wallet. 17 | account=0 18 | for f in /opt/validator_keys/keystore-*.json; do 19 | accountName="account-${account}" 20 | echo "Importing key ${f} into ethdo wallet: ${wallet}/${accountName}" 21 | 22 | KEYSTORE_PASSPHRASE=$(cat "${f//json/txt}") 23 | /app/ethdo \ 24 | --base-dir="${KEYS_DIR}" account import \ 25 | --account="${wallet}"/"${accountName}" \ 26 | --keystore="$f" \ 27 | --passphrase="$ACCOUNT_PASSPHRASE" \ 28 | --keystore-passphrase="$KEYSTORE_PASSPHRASE" \ 29 | --allow-weak-passphrases 30 | 31 | # Increment account. 32 | # shellcheck disable=SC2003 33 | account=$(expr "$account" + 1) 34 | done 35 | 36 | # Log wallet info. 37 | echo "Starting vouch validator client. Wallet info:" 38 | /app/ethdo wallet info \ 39 | --wallet="${wallet}" \ 40 | --base-dir="${KEYS_DIR}" \ 41 | --verbose 42 | 43 | # Now run vouch. 44 | exec /app/vouch --base-dir=${BASE_DIR} --beacon-node-address=${VOUCH_BEACON_NODE_ADDRESS} 45 | -------------------------------------------------------------------------------- /prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 30s # Set the scrape interval to every 30 seconds. 3 | evaluation_interval: 30s # Evaluate rules every 30 seconds. 4 | 5 | remote_write: 6 | - url: https://vm.monitoring.gcp.obol.tech/write 7 | authorization: 8 | credentials: $PROM_REMOTE_WRITE_TOKEN 9 | write_relabel_configs: 10 | - source_labels: [job] 11 | regex: "charon(.*)" 12 | action: keep # Keeps charon metrics and drop metrics from other containers. 13 | 14 | scrape_configs: 15 | - job_name: "charon-0" 16 | static_configs: 17 | - targets: ["node0:3620"] 18 | - job_name: "charon-1" 19 | static_configs: 20 | - targets: ["node1:3620"] 21 | - job_name: "charon-2" 22 | static_configs: 23 | - targets: ["node2:3620"] 24 | - job_name: "charon-3" 25 | static_configs: 26 | - targets: ["node3:3620"] 27 | - job_name: "charon-4" 28 | static_configs: 29 | - targets: ["node4:3620"] 30 | - job_name: "charon-5" 31 | static_configs: 32 | - targets: ["node5:3620"] 33 | - job_name: "vc0" 34 | static_configs: 35 | - targets: ["vc0-lighthouse:5064"] 36 | - job_name: "vc1" 37 | static_configs: 38 | - targets: ["vc1-teku:8008"] 39 | - job_name: "vc2" 40 | static_configs: 41 | - targets: ["vc2-nimbus:8108"] 42 | - job_name: "vc3" 43 | static_configs: 44 | - targets: ["vc3-lighthouse:5064"] 45 | - job_name: "vc4" 46 | static_configs: 47 | - targets: ["vc4-teku:8008"] 48 | - job_name: "vc5" 49 | static_configs: 50 | - targets: ["vc5-nimbus:8108"] 51 | - job_name: "node-exporter" 52 | static_configs: 53 | - targets: ["node-exporter:9100"] 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Obol Logo](https://obol.tech/obolnetwork.png) 2 | 3 |

Distributed Validator Cluster with Docker Compose

4 | 5 | This repo contains a [charon](https://github.com/ObolNetwork/charon) distributed validator cluster which you can run using [docker-compose](https://docs.docker.com/compose/). 6 | 7 | This repo aims to give users a feel for what a [Distributed Validator Cluster](https://docs.obol.tech/docs/int/key-concepts#distributed-validator-cluster) means in practice, and what the future of high-availability, fault-tolerant proof of stake validating deployments will look like. 8 | 9 | **This repo runs on a single machine, with only one execution and consensus client, you do not have fault tolerance with this setup, and this is only for demonstration purposes only, and should not be used in a production context.** 10 | 11 | A distributed validator cluster is a docker-compose file with the following containers running: 12 | 13 | - Single [Nethermind](https://github.com/NethermindEth/nethermind) execution layer client 14 | - Single [Lighthouse](https://github.com/sigp/lighthouse) consensus layer client 15 | - Six [charon](https://github.com/ObolNetwork/charon) Distributed Validator clients 16 | - Two [Lighthouse](https://github.com/sigp/lighthouse) Validator clients 17 | - Two [Teku](https://github.com/ConsenSys/teku) Validator Clients 18 | - Two [Nimbus](https://github.com/status-im/nimbus-eth2) Validator Clients 19 | - Prometheus, Grafana and Jaeger clients for monitoring this cluster. 20 | 21 | ![Distributed Validator Cluster](DVCluster.png) 22 | 23 | In the future, this repo aims to contain compose files for every possible Execution, Beacon, and Validator client combinations that is possible with DVT. 24 | 25 | ## Quickstart 26 | 27 | You can view a quickstart guide for testing this repo out on our [docs site](https://docs.obol.tech/docs/start/quickstart_alone). 28 | 29 | ## Project Status 30 | 31 | See [dvt.obol.tech](https://dvt.obol.tech/) for the latest status of the Obol Network including which upstream consensus clients and which downstream validators are supported. 32 | 33 | > Remember: Please make sure any existing validator has been shut down for 34 | > at least 3 finalised epochs before starting the charon cluster, 35 | > otherwise your validator could be slashed. 36 | 37 | # Troubleshooting 38 | 39 | [Check the docs](https://docs.obol.tech/docs/faq/errors) for some common errors and how to fix them. 40 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # This is a sample environment file that allows overriding default configuration defined 2 | # in docker-compose.yml. Rename this file to `.env` and then uncomment and set any variable below. 3 | 4 | # Supported ethereum network (for ex: mainnet, holesky, sepolia, gnosis) . 5 | #ETH2_NETWORK= 6 | 7 | ######### Nethermind Config ######### 8 | 9 | # Nethermind docker container image version, e.g. `latest` or `1.25.3`. 10 | # See available tags https://hub.docker.com/r/nethermind/nethermind/tags 11 | #NETHERMIND_VERSION= 12 | 13 | # Nethermind host exposed ports 14 | #NETHERMIND_PORT_P2P= 15 | #NETHERMIND_PORT_HTTP= 16 | #NETHERMIND_PORT_ENGINE= 17 | 18 | ######### Lighthouse Config ######### 19 | 20 | #LIGHTHOUSE_VERSION= 21 | #LIGHTHOUSE_PORT_P2P= 22 | #LIGHTHOUSE_PORT_HTTP= 23 | #LIGHTHOUSE_PORT_METRICS= 24 | #LIGHTHOUSE_EXECUTION_ENDPOINT= 25 | 26 | ######### Teku Config ######### 27 | 28 | # Teku validator client docker container image version, e.g. `latest` or `24.1.1`. 29 | # See available tags https://hub.docker.com/r/consensys/teku/tags 30 | #TEKU_VERSION= 31 | 32 | # Teku beacon node host exposed ports 33 | #TEKU_PORT_METRICS= 34 | 35 | ######### Charon Config ######### 36 | 37 | # Charon docker container image version, e.g. `v1.7.2`. 38 | # See available tags https://hub.docker.com/r/obolnetwork/charon/tags. 39 | #CHARON_VERSION= 40 | 41 | # Define custom relays. One or more ENRs or an http URL that return an ENR. Use a comma separated list excluding spaces. 42 | #CHARON_P2P_RELAYS= 43 | 44 | # Connect to one or more external beacon nodes. Use a comma separated list excluding spaces. 45 | #CHARON_BEACON_NODE_ENDPOINTS= 46 | 47 | # Pass HTTP Headers to all beacon node endpoints. (Comma separated, key=value strings). 48 | #CHARON_BEACON_NODE_HEADERS= 49 | 50 | # Override the charon logging level; debug, info, warning, error. 51 | #CHARON_LOG_LEVEL= 52 | 53 | # Override the charon logging format: console, logfmt, json. 54 | #CHARON_LOG_FORMAT= 55 | 56 | # Disable Jaeger tracing by setting an empty address. 57 | #CHARON_JAEGER_ADDRESS= 58 | 59 | # Advertise a custom external DNS hostname or IP address for UDP discv5 peer discovery. 60 | #CHARON_P2P_EXTERNAL_HOSTNAME= 61 | 62 | # Nickname to identify this charon node on monitoring (max 32 characters) 63 | #CHARON_NICKNAME= 64 | 65 | # Charon host exposed addresses 66 | #CHARON_VALIDATOR_API_ADDRESS= 67 | #CHARON_P2P_TCP_ADDRESS= 68 | #CHARON_MONITORING_ADDRESS= 69 | 70 | #PROMETHEUS_VERSION= 71 | #GRAFANA_VERSION= 72 | #NODE_EXPORTER_VERSION= 73 | #JAEGAR_VERSION= 74 | 75 | # Monitoring host exposed ports 76 | #MONITORING_PORT_PROMETHEUS= 77 | #MONITORING_PORT_GRAFANA= 78 | #MONITORING_PORT_JAEGER= 79 | -------------------------------------------------------------------------------- /docker-compose.override.yml.sample: -------------------------------------------------------------------------------- 1 | # The "Multiple Compose File" feature provides a very powerful way to override 2 | # any configuration in docker-compose.yml without needing to modify 3 | # git-checked-in files since that results in conflicts when upgrading this repo. 4 | # See https://docs.docker.com/compose/extends/#multiple-compose-files for more. 5 | 6 | # Just copy this file to `docker-compose.override.yml` and customise it to your liking. 7 | # `cp docker-compose.override.yml.sample docker-compose.override.yml` 8 | 9 | # Some example overrides are commented out below. Any uncommented section 10 | # below will automatically override the same section in 11 | # docker-compose.yml when ran with `docker-compose up`. 12 | # See https://docs.docker.com/compose/extends/#adding-and-overriding-configuration for details. 13 | 14 | # WARNING: This is for power users only and requires a deep understanding of Docker Compose 15 | # and how the local docker-compose.yml is configured. 16 | 17 | #services: 18 | #nethermind: 19 | # Disable nethermind 20 | #profiles: [disable] 21 | # Bind nethermind internal ports to host ports 22 | #ports: 23 | #- 8545:8545 # JSON-RPC 24 | #- 8551:8551 # AUTH-RPC 25 | #- 6060:6060 # Metrics 26 | 27 | #lighthouse: 28 | # Disable lighthouse 29 | #profiles: [disable] 30 | # Bind lighthouse internal ports to host ports 31 | #ports: 32 | #- 5052:5052 # HTTP 33 | #- 5054:5054 # Metrics 34 | 35 | #vc0-lighthouse: 36 | # Disable vc0-lighthouse 37 | #profiles: [disable] 38 | # Bind vc0-lighthouse internal ports to host ports 39 | #ports: 40 | #- 5064:5064 # Metrics 41 | 42 | #vc1-teku: 43 | # Disable teku 44 | #profiles: [disable] 45 | # Bind teku internal ports to host ports 46 | #ports: 47 | #- 8008:8008 # Metrics 48 | 49 | #vc2-nimbus: 50 | # Disable vc2-nimbus 51 | #profiles: [disable] 52 | # Bind vc2-nimbus internal ports to host ports 53 | #ports: 54 | #- 8108:8108 # Metrics 55 | 56 | #vc3-lighthouse: 57 | # Disable vc3-lighthouse 58 | #profiles: [disable] 59 | # Bind vc3-lighthouse internal ports to host ports 60 | #ports: 61 | #- 5064:5064 # Metrics 62 | 63 | #vc4-teku: 64 | # Disable vc1-teku 65 | #profiles: [disable] 66 | # Bind teku internal ports to host ports 67 | #ports: 68 | #- 8008:8008 # Metrics 69 | 70 | #vc5-nimbus: 71 | # Disable vc5-nimbus 72 | #profiles: [disable] 73 | # Bind vc5-nimbus internal ports to host ports 74 | #ports: 75 | #- 8108:8108 # Metrics 76 | 77 | #prometheus: 78 | # Disable prometheus 79 | #profiles: [disable] 80 | # Bind prometheus internal ports to host ports 81 | #ports: 82 | #- 9090:9090 # Metrics 83 | -------------------------------------------------------------------------------- /grafana/dashboards/simnet_dash.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 1, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": null, 27 | "fill": 1, 28 | "fillGradient": 0, 29 | "gridPos": { 30 | "h": 8, 31 | "w": 12, 32 | "x": 0, 33 | "y": 0 34 | }, 35 | "hiddenSeries": false, 36 | "id": 6, 37 | "legend": { 38 | "avg": false, 39 | "current": false, 40 | "max": false, 41 | "min": false, 42 | "show": true, 43 | "total": false, 44 | "values": false 45 | }, 46 | "lines": true, 47 | "linewidth": 1, 48 | "nullPointMode": "null", 49 | "options": { 50 | "dataLinks": [] 51 | }, 52 | "percentage": false, 53 | "pointradius": 2, 54 | "points": false, 55 | "renderer": "flot", 56 | "seriesOverrides": [], 57 | "spaceLength": 10, 58 | "stack": false, 59 | "steppedLine": false, 60 | "targets": [ 61 | { 62 | "expr": "app_start_time_secs", 63 | "interval": "", 64 | "legendFormat": "{{job}}", 65 | "refId": "A" 66 | } 67 | ], 68 | "thresholds": [], 69 | "timeFrom": null, 70 | "timeRegions": [], 71 | "timeShift": null, 72 | "title": "Panel Title", 73 | "tooltip": { 74 | "shared": true, 75 | "sort": 0, 76 | "value_type": "individual" 77 | }, 78 | "type": "graph", 79 | "xaxis": { 80 | "buckets": null, 81 | "mode": "time", 82 | "name": null, 83 | "show": true, 84 | "values": [] 85 | }, 86 | "yaxes": [ 87 | { 88 | "format": "short", 89 | "label": null, 90 | "logBase": 1, 91 | "max": null, 92 | "min": null, 93 | "show": true 94 | }, 95 | { 96 | "format": "short", 97 | "label": null, 98 | "logBase": 1, 99 | "max": null, 100 | "min": null, 101 | "show": true 102 | } 103 | ], 104 | "yaxis": { 105 | "align": false, 106 | "alignLevel": null 107 | } 108 | }, 109 | { 110 | "cacheTimeout": null, 111 | "datasource": null, 112 | "gridPos": { 113 | "h": 8, 114 | "w": 12, 115 | "x": 12, 116 | "y": 0 117 | }, 118 | "id": 4, 119 | "links": [], 120 | "options": { 121 | "colorMode": "value", 122 | "fieldOptions": { 123 | "calcs": [ 124 | "last" 125 | ], 126 | "defaults": { 127 | "mappings": [ 128 | { 129 | "id": 0, 130 | "op": "=", 131 | "text": "N/A", 132 | "type": 1, 133 | "value": "null" 134 | } 135 | ], 136 | "nullValueMode": "connected", 137 | "thresholds": { 138 | "mode": "absolute", 139 | "steps": [ 140 | { 141 | "color": "green", 142 | "value": null 143 | } 144 | ] 145 | }, 146 | "unit": "none" 147 | }, 148 | "overrides": [], 149 | "values": false 150 | }, 151 | "graphMode": "area", 152 | "justifyMode": "auto", 153 | "orientation": "horizontal" 154 | }, 155 | "pluginVersion": "6.7.3", 156 | "targets": [ 157 | { 158 | "expr": "core_scheduler_current_slot", 159 | "interval": "", 160 | "legendFormat": "{{job}}", 161 | "refId": "A" 162 | } 163 | ], 164 | "timeFrom": null, 165 | "timeShift": null, 166 | "title": "Charon Node Observed Slot", 167 | "type": "stat" 168 | }, 169 | { 170 | "aliasColors": {}, 171 | "bars": false, 172 | "dashLength": 10, 173 | "dashes": false, 174 | "datasource": "Prometheus", 175 | "fill": 1, 176 | "fillGradient": 0, 177 | "gridPos": { 178 | "h": 9, 179 | "w": 12, 180 | "x": 0, 181 | "y": 8 182 | }, 183 | "hiddenSeries": false, 184 | "id": 2, 185 | "legend": { 186 | "avg": false, 187 | "current": false, 188 | "max": false, 189 | "min": false, 190 | "show": true, 191 | "total": false, 192 | "values": false 193 | }, 194 | "lines": true, 195 | "linewidth": 1, 196 | "nullPointMode": "null", 197 | "options": { 198 | "dataLinks": [] 199 | }, 200 | "percentage": false, 201 | "pointradius": 2, 202 | "points": false, 203 | "renderer": "flot", 204 | "seriesOverrides": [], 205 | "spaceLength": 10, 206 | "stack": false, 207 | "steppedLine": false, 208 | "targets": [ 209 | { 210 | "expr": "app_eth2_latency_seconds_sum", 211 | "instant": false, 212 | "interval": "", 213 | "legendFormat": "", 214 | "refId": "A" 215 | } 216 | ], 217 | "thresholds": [], 218 | "timeFrom": null, 219 | "timeRegions": [], 220 | "timeShift": null, 221 | "title": "Eth2 Latency", 222 | "tooltip": { 223 | "shared": true, 224 | "sort": 0, 225 | "value_type": "individual" 226 | }, 227 | "type": "graph", 228 | "xaxis": { 229 | "buckets": null, 230 | "mode": "time", 231 | "name": null, 232 | "show": true, 233 | "values": [] 234 | }, 235 | "yaxes": [ 236 | { 237 | "format": "short", 238 | "label": null, 239 | "logBase": 1, 240 | "max": null, 241 | "min": null, 242 | "show": true 243 | }, 244 | { 245 | "format": "short", 246 | "label": null, 247 | "logBase": 1, 248 | "max": null, 249 | "min": null, 250 | "show": true 251 | } 252 | ], 253 | "yaxis": { 254 | "align": false, 255 | "alignLevel": null 256 | } 257 | } 258 | ], 259 | "refresh": "10s", 260 | "schemaVersion": 22, 261 | "style": "dark", 262 | "tags": [], 263 | "templating": { 264 | "list": [] 265 | }, 266 | "time": { 267 | "from": "now-6h", 268 | "to": "now" 269 | }, 270 | "timepicker": { 271 | "refresh_intervals": [ 272 | "5s", 273 | "10s", 274 | "30s", 275 | "1m", 276 | "5m", 277 | "15m", 278 | "30m", 279 | "1h", 280 | "2h", 281 | "1d" 282 | ] 283 | }, 284 | "timezone": "", 285 | "title": "Simnet Dashboard", 286 | "uid": "laEp8vu7z", 287 | "variables": { 288 | "list": [] 289 | }, 290 | "version": 1 291 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | x-node-base: 2 | # Pegged charon version (update this for each release). 3 | &node-base 4 | image: obolnetwork/charon:${CHARON_VERSION:-v1.7.2} 5 | restart: unless-stopped 6 | networks: [cluster] 7 | depends_on: [relay] 8 | volumes: 9 | - ./.charon:/opt/charon/.charon/ 10 | 11 | x-node-env: &node-env 12 | CHARON_BEACON_NODE_ENDPOINTS: ${CHARON_BEACON_NODE_ENDPOINTS:-http://lighthouse:5052} 13 | CHARON_BEACON_NODE_HEADERS: ${CHARON_BEACON_NODE_HEADERS:-IgnoredHTTP=header} 14 | CHARON_LOG_LEVEL: ${CHARON_LOG_LEVEL:-info} 15 | CHARON_LOG_FORMAT: ${CHARON_LOG_FORMAT:-console} 16 | CHARON_P2P_EXTERNAL_HOSTNAME: ${CHARON_P2P_EXTERNAL_HOSTNAME:-} # Empty default required to avoid warnings. 17 | CHARON_P2P_RELAYS: ${CHARON_P2P_RELAYS:-http://relay:3640/enr} 18 | CHARON_P2P_TCP_ADDRESS: ${CHARON_P2P_TCP_ADDRESS:-0.0.0.0:3610} 19 | CHARON_VALIDATOR_API_ADDRESS: ${CHARON_VALIDATOR_API_ADDRESS:-0.0.0.0:3600} 20 | CHARON_MONITORING_ADDRESS: ${CHARON_MONITORING_ADDRESS:-0.0.0.0:3620} 21 | CHARON_JAEGER_ADDRESS: ${CHARON_JAEGER_ADDRESS-jaeger:6831} # Overriding to empty address allowed 22 | CHARON_NICKNAME: ${CHARON_NICKNAME:-} 23 | 24 | services: 25 | # _ _ _ _ 26 | # _ __ ___| |_| |__ ___ _ __ _ __ ___ (_)_ __ __| | 27 | # | '_ \ / _ \ __| '_ \ / _ \ '__| '_ ` _ \| | '_ \ / _` | 28 | # | | | | __/ |_| | | | __/ | | | | | | | | | | | (_| | 29 | # |_| |_|\___|\__|_| |_|\___|_| |_| |_| |_|_|_| |_|\__,_| 30 | nethermind: 31 | image: nethermind/nethermind:${NETHERMIND_VERSION:-1.35.0} 32 | restart: unless-stopped 33 | ports: 34 | - ${NETHERMIND_PORT_P2P:-30303}:30303/tcp # P2P TCP 35 | - ${NETHERMIND_PORT_P2P:-30303}:30303/udp # P2P UDP 36 | - ${NETHERMIND_PORT_HTTP:-8545}:8545 # JSON-RPC 37 | - ${NETHERMIND_PORT_ENGINE:-8551}:8551 # ENGINE-API 38 | command: | 39 | --config=${ETH2_NETWORK:-holesky} 40 | --datadir=data 41 | --HealthChecks.Enabled=true 42 | --JsonRpc.Enabled=true 43 | --JsonRpc.JwtSecretFile="/root/jwt/jwt.hex" 44 | --JsonRpc.EngineHost=0.0.0.0 45 | --JsonRpc.EnginePort=8551 46 | --JsonRpc.Host=0.0.0.0 47 | --JsonRpc.Port=8545 48 | --Sync.SnapSync=true 49 | --Sync.AncientBodiesBarrier=4367322 50 | --Sync.AncientReceiptsBarrier=4367322 51 | networks: [cluster] 52 | volumes: 53 | - ./data/nethermind:/nethermind/data 54 | - ./jwt:/root/jwt 55 | 56 | # _ _ _ _ _ 57 | # | (_) __ _| |__ | |_| |__ ___ _ _ ___ ___ 58 | # | | |/ _` | '_ \| __| '_ \ / _ \| | | / __|/ _ \ 59 | # | | | (_| | | | | |_| | | | (_) | |_| \__ \ __/ 60 | # |_|_|\__, |_| |_|\__|_| |_|\___/ \__,_|___/\___| 61 | # |___/ 62 | 63 | lighthouse: 64 | image: sigp/lighthouse:${LIGHTHOUSE_VERSION:-v8.0.0-rc.2} 65 | ports: 66 | - ${LIGHTHOUSE_PORT_P2P:-9000}:9000/tcp # P2P TCP 67 | - ${LIGHTHOUSE_PORT_P2P:-9000}:9000/udp # P2P UDP 68 | - ${LIGHTHOUSE_PORT_HTTP:-5052}:5052 # HTTP API 69 | - ${LIGHTHOUSE_PORT_METRICS:-5054}:5054 # Metrics 70 | command: | 71 | lighthouse bn 72 | --network=${ETH2_NETWORK:-holesky} 73 | --checkpoint-sync-url=https://checkpoint-sync.holesky.ethpandaops.io/ 74 | --execution-endpoint=${LIGHTHOUSE_EXECUTION_ENDPOINT:-http://nethermind:8551} 75 | --execution-jwt=/opt/jwt/jwt.hex 76 | --datadir=/opt/app/beacon/ 77 | --debug-level=info 78 | --http 79 | --http-address=0.0.0.0 80 | --http-port=5052 81 | --metrics 82 | --metrics-address=0.0.0.0 83 | --metrics-port=5054 84 | --metrics-allow-origin="*" 85 | networks: [cluster] 86 | volumes: 87 | - ./data/lighthouse:/opt/app/beacon 88 | - ./jwt:/opt/jwt 89 | restart: unless-stopped 90 | 91 | # _ _ _ _ 92 | # ___| |__ __ _ _ __ ___ _ __ ___| (_) ___ _ __ | |_ ___ 93 | # / __| '_ \ / _` | '__/ _ \| '_ \ / __| | |/ _ \ '_ \| __/ __| 94 | # | (__| | | | (_| | | | (_) | | | | | (__| | | __/ | | | |_\__ \ 95 | # \___|_| |_|\__,_|_| \___/|_| |_| \___|_|_|\___|_| |_|\__|___/ 96 | node0: 97 | <<: *node-base 98 | environment: 99 | <<: *node-env 100 | CHARON_PRIVATE_KEY_FILE: /opt/charon/.charon/cluster/node0/charon-enr-private-key 101 | CHARON_LOCK_FILE: /opt/charon/.charon/cluster/node0/cluster-lock.json 102 | CHARON_JAEGER_SERVICE: node0 103 | CHARON_P2P_EXTERNAL_HOSTNAME: node0 104 | 105 | node1: 106 | <<: *node-base 107 | environment: 108 | <<: *node-env 109 | CHARON_PRIVATE_KEY_FILE: /opt/charon/.charon/cluster/node1/charon-enr-private-key 110 | CHARON_LOCK_FILE: /opt/charon/.charon/cluster/node1/cluster-lock.json 111 | CHARON_JAEGER_SERVICE: node1 112 | CHARON_P2P_EXTERNAL_HOSTNAME: node1 113 | 114 | node2: 115 | <<: *node-base 116 | environment: 117 | <<: *node-env 118 | CHARON_PRIVATE_KEY_FILE: /opt/charon/.charon/cluster/node2/charon-enr-private-key 119 | CHARON_LOCK_FILE: /opt/charon/.charon/cluster/node2/cluster-lock.json 120 | CHARON_JAEGER_SERVICE: node2 121 | CHARON_P2P_EXTERNAL_HOSTNAME: node2 122 | 123 | node3: 124 | <<: *node-base 125 | environment: 126 | <<: *node-env 127 | CHARON_PRIVATE_KEY_FILE: /opt/charon/.charon/cluster/node3/charon-enr-private-key 128 | CHARON_LOCK_FILE: /opt/charon/.charon/cluster/node3/cluster-lock.json 129 | CHARON_JAEGER_SERVICE: node3 130 | CHARON_P2P_EXTERNAL_HOSTNAME: node3 131 | 132 | node4: 133 | <<: *node-base 134 | environment: 135 | <<: *node-env 136 | CHARON_PRIVATE_KEY_FILE: /opt/charon/.charon/cluster/node4/charon-enr-private-key 137 | CHARON_LOCK_FILE: /opt/charon/.charon/cluster/node4/cluster-lock.json 138 | CHARON_JAEGER_SERVICE: node4 139 | CHARON_P2P_EXTERNAL_HOSTNAME: node4 140 | 141 | node5: 142 | <<: *node-base 143 | environment: 144 | <<: *node-env 145 | CHARON_PRIVATE_KEY_FILE: /opt/charon/.charon/cluster/node5/charon-enr-private-key 146 | CHARON_LOCK_FILE: /opt/charon/.charon/cluster/node5/cluster-lock.json 147 | CHARON_JAEGER_SERVICE: node5 148 | CHARON_P2P_EXTERNAL_HOSTNAME: node5 149 | 150 | relay: 151 | <<: *node-base 152 | command: relay 153 | depends_on: [] 154 | environment: 155 | <<: *node-env 156 | CHARON_HTTP_ADDRESS: 0.0.0.0:3640 157 | CHARON_DATA_DIR: /opt/charon/relay 158 | CHARON_P2P_RELAYS: "" 159 | CHARON_P2P_EXTERNAL_HOSTNAME: relay 160 | volumes: 161 | - ./relay:/opt/charon/relay:rw 162 | 163 | # _ _ _ _ 164 | # __ ____ _| (_) __| | __ _| |_ ___ _ __ ___ 165 | # \ \ / / _` | | |/ _` |/ _` | __/ _ \| '__/ __| 166 | # \ V / (_| | | | (_| | (_| | || (_) | | \__ \ 167 | # \_/ \__,_|_|_|\__,_|\__,_|\__\___/|_| |___/ 168 | vc0-lighthouse: 169 | image: sigp/lighthouse:${LIGHTHOUSE_VERSION:-v8.0.0-rc.2} 170 | entrypoint: /opt/lighthouse/run.sh 171 | networks: [cluster] 172 | depends_on: [node0] 173 | restart: unless-stopped 174 | environment: 175 | LIGHTHOUSE_BEACON_NODE_ADDRESS: http://node0:3600 176 | ETH2_NETWORK: ${ETH2_NETWORK:-holesky} 177 | volumes: 178 | - ./lighthouse/run.sh:/opt/lighthouse/run.sh 179 | - .charon/cluster/node0/validator_keys:/opt/charon/keys 180 | 181 | vc1-teku: 182 | image: consensys/teku:${TEKU_VERSION:-25.10.0} 183 | networks: [cluster] 184 | depends_on: [node1] 185 | restart: unless-stopped 186 | command: | 187 | validator-client 188 | --beacon-node-api-endpoint="http://node1:3600" 189 | --config-file "/opt/charon/teku/teku-config.yaml" 190 | volumes: 191 | - .charon/cluster/node1/validator_keys:/opt/charon/validator_keys 192 | - ./teku:/opt/charon/teku 193 | 194 | vc2-nimbus: 195 | build: nimbus 196 | networks: [cluster] 197 | depends_on: [node2] 198 | restart: unless-stopped 199 | environment: 200 | NODE: node2 201 | volumes: 202 | - .charon/cluster/node2/validator_keys:/home/validator_keys 203 | - ./nimbus:/home/user/data 204 | 205 | vc3-lighthouse: 206 | image: sigp/lighthouse:${LIGHTHOUSE_VERSION:-v8.0.0-rc.2} 207 | entrypoint: /opt/lighthouse/run.sh 208 | networks: [cluster] 209 | depends_on: [node3] 210 | restart: unless-stopped 211 | environment: 212 | LIGHTHOUSE_BEACON_NODE_ADDRESS: http://node3:3600 213 | ETH2_NETWORK: ${ETH2_NETWORK:-holesky} 214 | volumes: 215 | - ./lighthouse/run.sh:/opt/lighthouse/run.sh 216 | - .charon/cluster/node3/validator_keys:/opt/charon/keys 217 | 218 | vc4-teku: 219 | image: consensys/teku:${TEKU_VERSION:-25.10.0} 220 | networks: [cluster] 221 | depends_on: [node4] 222 | restart: unless-stopped 223 | command: | 224 | validator-client 225 | --beacon-node-api-endpoint="http://node4:3600" 226 | --config-file "/opt/charon/teku/teku-config.yaml" 227 | volumes: 228 | - .charon/cluster/node4/validator_keys:/opt/charon/validator_keys 229 | - ./teku:/opt/charon/teku 230 | 231 | vc5-nimbus: 232 | build: nimbus 233 | networks: [cluster] 234 | depends_on: [node5] 235 | restart: unless-stopped 236 | environment: 237 | NODE: node5 238 | volumes: 239 | - .charon/cluster/node5/validator_keys:/home/validator_keys 240 | - ./nimbus:/home/user/data 241 | 242 | # _ _ _ 243 | # _ __ ___ ___ _ __ (_) |_ ___ _ __(_)_ __ __ _ 244 | # | '_ ` _ \ / _ \| '_ \| | __/ _ \| '__| | '_ \ / _` | 245 | # | | | | | | (_) | | | | | || (_) | | | | | | | (_| | 246 | # |_| |_| |_|\___/|_| |_|_|\__\___/|_| |_|_| |_|\__, | 247 | # |___/ 248 | prometheus: 249 | image: prom/prometheus:${PROMETHEUS_VERSION:-v3.7.2} 250 | ports: 251 | - "9090:9090" 252 | networks: [cluster] 253 | volumes: 254 | - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml 255 | 256 | grafana: 257 | image: grafana/grafana:${GRAFANA_VERSION:-12.2.1} 258 | ports: 259 | - "3000:3000" 260 | networks: [cluster] 261 | depends_on: [prometheus] 262 | volumes: 263 | - ./grafana/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml 264 | - ./grafana/dashboards.yml:/etc/grafana/provisioning/dashboards/datasource.yml 265 | - ./grafana/grafana.ini:/etc/grafana/grafana.ini:ro 266 | - ./grafana/dashboards:/etc/dashboards 267 | 268 | networks: 269 | cluster: 270 | -------------------------------------------------------------------------------- /grafana/dashboards/local_cluster_dash.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 2, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": null, 27 | "fill": 1, 28 | "fillGradient": 0, 29 | "gridPos": { 30 | "h": 8, 31 | "w": 12, 32 | "x": 0, 33 | "y": 0 34 | }, 35 | "hiddenSeries": false, 36 | "id": 8, 37 | "legend": { 38 | "avg": false, 39 | "current": false, 40 | "max": false, 41 | "min": false, 42 | "show": true, 43 | "total": false, 44 | "values": false 45 | }, 46 | "lines": true, 47 | "linewidth": 1, 48 | "nullPointMode": "null", 49 | "options": { 50 | "dataLinks": [] 51 | }, 52 | "percentage": false, 53 | "pointradius": 2, 54 | "points": false, 55 | "renderer": "flot", 56 | "seriesOverrides": [], 57 | "spaceLength": 10, 58 | "stack": false, 59 | "steppedLine": false, 60 | "targets": [ 61 | { 62 | "expr": "p2p_ping_latency_secs_sum{job=\"charon-0\"} / p2p_ping_latency_secs_count", 63 | "interval": "", 64 | "legendFormat": "{{job}}-{{peer}}", 65 | "refId": "A" 66 | } 67 | ], 68 | "thresholds": [], 69 | "timeFrom": null, 70 | "timeRegions": [], 71 | "timeShift": null, 72 | "title": "Peer Latency Observed by Charon 0", 73 | "tooltip": { 74 | "shared": true, 75 | "sort": 0, 76 | "value_type": "individual" 77 | }, 78 | "type": "graph", 79 | "xaxis": { 80 | "buckets": null, 81 | "mode": "time", 82 | "name": null, 83 | "show": true, 84 | "values": [] 85 | }, 86 | "yaxes": [ 87 | { 88 | "format": "short", 89 | "label": null, 90 | "logBase": 1, 91 | "max": null, 92 | "min": null, 93 | "show": true 94 | }, 95 | { 96 | "format": "short", 97 | "label": null, 98 | "logBase": 1, 99 | "max": null, 100 | "min": null, 101 | "show": true 102 | } 103 | ], 104 | "yaxis": { 105 | "align": false, 106 | "alignLevel": null 107 | } 108 | }, 109 | { 110 | "aliasColors": {}, 111 | "bars": false, 112 | "dashLength": 10, 113 | "dashes": false, 114 | "datasource": null, 115 | "fill": 1, 116 | "fillGradient": 0, 117 | "gridPos": { 118 | "h": 8, 119 | "w": 12, 120 | "x": 12, 121 | "y": 0 122 | }, 123 | "hiddenSeries": false, 124 | "id": 10, 125 | "legend": { 126 | "avg": false, 127 | "current": false, 128 | "max": false, 129 | "min": false, 130 | "show": true, 131 | "total": false, 132 | "values": false 133 | }, 134 | "lines": true, 135 | "linewidth": 1, 136 | "nullPointMode": "null", 137 | "options": { 138 | "dataLinks": [] 139 | }, 140 | "percentage": false, 141 | "pointradius": 2, 142 | "points": false, 143 | "renderer": "flot", 144 | "seriesOverrides": [], 145 | "spaceLength": 10, 146 | "stack": false, 147 | "steppedLine": false, 148 | "targets": [ 149 | { 150 | "expr": "p2p_ping_latency_secs_sum{job=\"charon-1\"} / p2p_ping_latency_secs_count", 151 | "interval": "", 152 | "legendFormat": "{{job}}-{{peer}}", 153 | "refId": "A" 154 | } 155 | ], 156 | "thresholds": [], 157 | "timeFrom": null, 158 | "timeRegions": [], 159 | "timeShift": null, 160 | "title": "Peer Latency Observed by Charon 1", 161 | "tooltip": { 162 | "shared": true, 163 | "sort": 0, 164 | "value_type": "individual" 165 | }, 166 | "type": "graph", 167 | "xaxis": { 168 | "buckets": null, 169 | "mode": "time", 170 | "name": null, 171 | "show": true, 172 | "values": [] 173 | }, 174 | "yaxes": [ 175 | { 176 | "format": "short", 177 | "label": null, 178 | "logBase": 1, 179 | "max": null, 180 | "min": null, 181 | "show": true 182 | }, 183 | { 184 | "format": "short", 185 | "label": null, 186 | "logBase": 1, 187 | "max": null, 188 | "min": null, 189 | "show": true 190 | } 191 | ], 192 | "yaxis": { 193 | "align": false, 194 | "alignLevel": null 195 | } 196 | }, 197 | { 198 | "aliasColors": {}, 199 | "bars": false, 200 | "dashLength": 10, 201 | "dashes": false, 202 | "datasource": null, 203 | "fill": 1, 204 | "fillGradient": 0, 205 | "gridPos": { 206 | "h": 8, 207 | "w": 12, 208 | "x": 0, 209 | "y": 8 210 | }, 211 | "hiddenSeries": false, 212 | "id": 11, 213 | "legend": { 214 | "avg": false, 215 | "current": false, 216 | "max": false, 217 | "min": false, 218 | "show": true, 219 | "total": false, 220 | "values": false 221 | }, 222 | "lines": true, 223 | "linewidth": 1, 224 | "nullPointMode": "null", 225 | "options": { 226 | "dataLinks": [] 227 | }, 228 | "percentage": false, 229 | "pointradius": 2, 230 | "points": false, 231 | "renderer": "flot", 232 | "seriesOverrides": [], 233 | "spaceLength": 10, 234 | "stack": false, 235 | "steppedLine": false, 236 | "targets": [ 237 | { 238 | "expr": "p2p_ping_latency_secs_sum{job=\"charon-2\"} / p2p_ping_latency_secs_count", 239 | "interval": "", 240 | "legendFormat": "{{job}}-{{peer}}", 241 | "refId": "A" 242 | } 243 | ], 244 | "thresholds": [], 245 | "timeFrom": null, 246 | "timeRegions": [], 247 | "timeShift": null, 248 | "title": "Peer Latency Observed by Charon 2", 249 | "tooltip": { 250 | "shared": true, 251 | "sort": 0, 252 | "value_type": "individual" 253 | }, 254 | "type": "graph", 255 | "xaxis": { 256 | "buckets": null, 257 | "mode": "time", 258 | "name": null, 259 | "show": true, 260 | "values": [] 261 | }, 262 | "yaxes": [ 263 | { 264 | "format": "short", 265 | "label": null, 266 | "logBase": 1, 267 | "max": null, 268 | "min": null, 269 | "show": true 270 | }, 271 | { 272 | "format": "short", 273 | "label": null, 274 | "logBase": 1, 275 | "max": null, 276 | "min": null, 277 | "show": true 278 | } 279 | ], 280 | "yaxis": { 281 | "align": false, 282 | "alignLevel": null 283 | } 284 | }, 285 | { 286 | "aliasColors": {}, 287 | "bars": false, 288 | "dashLength": 10, 289 | "dashes": false, 290 | "datasource": null, 291 | "fill": 1, 292 | "fillGradient": 0, 293 | "gridPos": { 294 | "h": 8, 295 | "w": 12, 296 | "x": 12, 297 | "y": 8 298 | }, 299 | "hiddenSeries": false, 300 | "id": 9, 301 | "legend": { 302 | "avg": false, 303 | "current": false, 304 | "max": false, 305 | "min": false, 306 | "show": true, 307 | "total": false, 308 | "values": false 309 | }, 310 | "lines": true, 311 | "linewidth": 1, 312 | "nullPointMode": "null", 313 | "options": { 314 | "dataLinks": [] 315 | }, 316 | "percentage": false, 317 | "pointradius": 2, 318 | "points": false, 319 | "renderer": "flot", 320 | "seriesOverrides": [], 321 | "spaceLength": 10, 322 | "stack": false, 323 | "steppedLine": false, 324 | "targets": [ 325 | { 326 | "expr": "p2p_ping_latency_secs_sum{job=\"charon-3\"} / p2p_ping_latency_secs_count", 327 | "interval": "", 328 | "legendFormat": "{{job}}-{{peer}}", 329 | "refId": "A" 330 | } 331 | ], 332 | "thresholds": [], 333 | "timeFrom": null, 334 | "timeRegions": [], 335 | "timeShift": null, 336 | "title": "Peer Latency Observed by Charon 3", 337 | "tooltip": { 338 | "shared": true, 339 | "sort": 0, 340 | "value_type": "individual" 341 | }, 342 | "type": "graph", 343 | "xaxis": { 344 | "buckets": null, 345 | "mode": "time", 346 | "name": null, 347 | "show": true, 348 | "values": [] 349 | }, 350 | "yaxes": [ 351 | { 352 | "format": "short", 353 | "label": null, 354 | "logBase": 1, 355 | "max": null, 356 | "min": null, 357 | "show": true 358 | }, 359 | { 360 | "format": "short", 361 | "label": null, 362 | "logBase": 1, 363 | "max": null, 364 | "min": null, 365 | "show": true 366 | } 367 | ], 368 | "yaxis": { 369 | "align": false, 370 | "alignLevel": null 371 | } 372 | }, 373 | { 374 | "datasource": null, 375 | "gridPos": { 376 | "h": 8, 377 | "w": 12, 378 | "x": 0, 379 | "y": 16 380 | }, 381 | "id": 6, 382 | "options": { 383 | "colorMode": "value", 384 | "fieldOptions": { 385 | "calcs": [ 386 | "mean" 387 | ], 388 | "defaults": { 389 | "mappings": [], 390 | "thresholds": { 391 | "mode": "absolute", 392 | "steps": [ 393 | { 394 | "color": "green", 395 | "value": null 396 | } 397 | ] 398 | }, 399 | "unit": "dateTimeAsIso" 400 | }, 401 | "overrides": [], 402 | "values": false 403 | }, 404 | "graphMode": "area", 405 | "justifyMode": "auto", 406 | "orientation": "auto" 407 | }, 408 | "pluginVersion": "6.7.3", 409 | "targets": [ 410 | { 411 | "expr": "app_start_time_secs * 1000", 412 | "interval": "", 413 | "legendFormat": "{{job}}", 414 | "refId": "A" 415 | } 416 | ], 417 | "timeFrom": null, 418 | "timeShift": null, 419 | "title": "Node start time", 420 | "type": "stat" 421 | }, 422 | { 423 | "cacheTimeout": null, 424 | "datasource": null, 425 | "gridPos": { 426 | "h": 8, 427 | "w": 12, 428 | "x": 12, 429 | "y": 16 430 | }, 431 | "id": 4, 432 | "links": [], 433 | "options": { 434 | "colorMode": "value", 435 | "fieldOptions": { 436 | "calcs": [ 437 | "mean" 438 | ], 439 | "defaults": { 440 | "mappings": [ 441 | { 442 | "id": 0, 443 | "op": "=", 444 | "text": "N/A", 445 | "type": 1, 446 | "value": "null" 447 | } 448 | ], 449 | "nullValueMode": "connected", 450 | "thresholds": { 451 | "mode": "absolute", 452 | "steps": [ 453 | { 454 | "color": "green", 455 | "value": null 456 | } 457 | ] 458 | }, 459 | "unit": "none" 460 | }, 461 | "overrides": [], 462 | "values": false 463 | }, 464 | "graphMode": "area", 465 | "justifyMode": "auto", 466 | "orientation": "horizontal" 467 | }, 468 | "pluginVersion": "6.7.3", 469 | "targets": [ 470 | { 471 | "expr": "core_scheduler_current_slot", 472 | "interval": "", 473 | "legendFormat": "{{job}}", 474 | "refId": "A" 475 | } 476 | ], 477 | "timeFrom": null, 478 | "timeShift": null, 479 | "title": "Latest Slot", 480 | "type": "stat" 481 | }, 482 | { 483 | "aliasColors": {}, 484 | "bars": false, 485 | "dashLength": 10, 486 | "dashes": false, 487 | "datasource": "Prometheus", 488 | "description": "Total request length / Total number of requests made to beacon nodes by charon clients", 489 | "fill": 1, 490 | "fillGradient": 0, 491 | "gridPos": { 492 | "h": 9, 493 | "w": 12, 494 | "x": 0, 495 | "y": 24 496 | }, 497 | "hiddenSeries": false, 498 | "id": 2, 499 | "legend": { 500 | "avg": false, 501 | "current": false, 502 | "max": false, 503 | "min": false, 504 | "show": true, 505 | "total": false, 506 | "values": false 507 | }, 508 | "lines": true, 509 | "linewidth": 1, 510 | "nullPointMode": "null", 511 | "options": { 512 | "dataLinks": [] 513 | }, 514 | "percentage": false, 515 | "pointradius": 2, 516 | "points": false, 517 | "renderer": "flot", 518 | "seriesOverrides": [], 519 | "spaceLength": 10, 520 | "stack": false, 521 | "steppedLine": false, 522 | "targets": [ 523 | { 524 | "expr": "app_eth2_latency_seconds_sum / app_eth2_latency_seconds_count", 525 | "instant": false, 526 | "interval": "", 527 | "legendFormat": "{{job}}-{{endpoint}}", 528 | "refId": "A" 529 | } 530 | ], 531 | "thresholds": [], 532 | "timeFrom": null, 533 | "timeRegions": [], 534 | "timeShift": null, 535 | "title": "Beacon Node Latency", 536 | "tooltip": { 537 | "shared": true, 538 | "sort": 0, 539 | "value_type": "individual" 540 | }, 541 | "type": "graph", 542 | "xaxis": { 543 | "buckets": null, 544 | "mode": "time", 545 | "name": null, 546 | "show": true, 547 | "values": [] 548 | }, 549 | "yaxes": [ 550 | { 551 | "format": "short", 552 | "label": "Seconds", 553 | "logBase": 1, 554 | "max": null, 555 | "min": null, 556 | "show": true 557 | }, 558 | { 559 | "format": "short", 560 | "label": null, 561 | "logBase": 1, 562 | "max": null, 563 | "min": null, 564 | "show": true 565 | } 566 | ], 567 | "yaxis": { 568 | "align": false, 569 | "alignLevel": null 570 | } 571 | }, 572 | { 573 | "aliasColors": {}, 574 | "bars": false, 575 | "dashLength": 10, 576 | "dashes": false, 577 | "datasource": null, 578 | "fill": 1, 579 | "fillGradient": 0, 580 | "gridPos": { 581 | "h": 8, 582 | "w": 12, 583 | "x": 12, 584 | "y": 24 585 | }, 586 | "hiddenSeries": false, 587 | "id": 13, 588 | "legend": { 589 | "avg": false, 590 | "current": false, 591 | "max": false, 592 | "min": false, 593 | "show": true, 594 | "total": false, 595 | "values": false 596 | }, 597 | "lines": true, 598 | "linewidth": 1, 599 | "nullPointMode": "null", 600 | "options": { 601 | "dataLinks": [] 602 | }, 603 | "percentage": false, 604 | "pointradius": 2, 605 | "points": false, 606 | "renderer": "flot", 607 | "seriesOverrides": [], 608 | "spaceLength": 10, 609 | "stack": false, 610 | "steppedLine": false, 611 | "targets": [ 612 | { 613 | "expr": "rate(p2p_ping_error_total[5m])", 614 | "interval": "", 615 | "legendFormat": "", 616 | "refId": "A" 617 | } 618 | ], 619 | "thresholds": [], 620 | "timeFrom": null, 621 | "timeRegions": [], 622 | "timeShift": null, 623 | "title": "Ping Errors between clients in the last 5 minutes", 624 | "tooltip": { 625 | "shared": true, 626 | "sort": 0, 627 | "value_type": "individual" 628 | }, 629 | "type": "graph", 630 | "xaxis": { 631 | "buckets": null, 632 | "mode": "time", 633 | "name": null, 634 | "show": true, 635 | "values": [] 636 | }, 637 | "yaxes": [ 638 | { 639 | "format": "short", 640 | "label": null, 641 | "logBase": 1, 642 | "max": null, 643 | "min": null, 644 | "show": true 645 | }, 646 | { 647 | "format": "short", 648 | "label": null, 649 | "logBase": 1, 650 | "max": null, 651 | "min": null, 652 | "show": true 653 | } 654 | ], 655 | "yaxis": { 656 | "align": false, 657 | "alignLevel": null 658 | } 659 | } 660 | ], 661 | "refresh": "10s", 662 | "schemaVersion": 22, 663 | "style": "dark", 664 | "tags": [], 665 | "templating": { 666 | "list": [] 667 | }, 668 | "time": { 669 | "from": "now-15m", 670 | "to": "now" 671 | }, 672 | "timepicker": { 673 | "refresh_intervals": [ 674 | "5s", 675 | "10s", 676 | "30s", 677 | "1m", 678 | "5m", 679 | "15m", 680 | "30m", 681 | "1h", 682 | "2h", 683 | "1d" 684 | ] 685 | }, 686 | "timezone": "", 687 | "title": "Local Docker Cluster Dashboard", 688 | "uid": "laEp8vupp", 689 | "variables": { 690 | "list": [] 691 | }, 692 | "version": 1 693 | } --------------------------------------------------------------------------------