From 4dc3abe6b7d9395b6d6fedae04af50614fb4d7cc Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Tue, 25 Aug 2026 16:26:19 +0530 Subject: [PATCH] feat: add opt-in Optimum Gateway compose overlay --- .env.sample.hoodi | 15 +++++++ .env.sample.mainnet | 15 +++++++ .gitignore | 5 +++ CLAUDE.md | 2 +- README.md | 6 +++ compose-optimum.yml | 26 +++++++++++ optimum/.env.optimum.sample | 20 +++++++++ optimum/README.md | 72 ++++++++++++++++++++++++++++++ optimum/config/sample.app_conf.yml | 15 +++++++ optimum/init-optimum.sh | 67 +++++++++++++++++++++++++++ 10 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 compose-optimum.yml create mode 100644 optimum/.env.optimum.sample create mode 100644 optimum/README.md create mode 100644 optimum/config/sample.app_conf.yml create mode 100755 optimum/init-optimum.sh diff --git a/.env.sample.hoodi b/.env.sample.hoodi index 56400c3b..79f71601 100644 --- a/.env.sample.hoodi +++ b/.env.sample.hoodi @@ -230,6 +230,21 @@ MEV_RELAYS=https://0x98f0ef62f00780cf8eb06701a7d22725b9437d4768bb19b363e882ae871 #EJECTOR_ALLOY_MONITORED=false #DV_EXIT_ALLOY_MONITORED=false +######### Optimum Gateway (opt-in) ######### + +# Adds getoptimum/gateway on the dvnode network. Off by default; Charon/VC/keys are unchanged. +# Docs: optimum/README.md +# 1. Set OPT_API_KEY and GATEWAY_CLUSTER_ID from Optimum onboarding (cluster id is not CLUSTER_NAME). +# 2. Append :compose-optimum.yml to COMPOSE_FILE above. Do not replace the existing list. +# 3. After the CL is up: ./optimum/init-optimum.sh && docker compose up -d optimum-gateway +#OPT_API_KEY= +#GATEWAY_CLUSTER_ID= +#GATEWAY_VERSION=v1.1.1 +#GATEWAY_PORT_LIBP2P=33212 +#GATEWAY_PORT_MUMP2P=43213 +#GATEWAY_PORT_TELEMETRY=48123 +#OPTIMUM_GATEWAY_ALLOY_MONITORED=false + ######### Debug Config ######### # This applies to compose-debug.yml only. diff --git a/.env.sample.mainnet b/.env.sample.mainnet index 2758a65b..6fa3018a 100644 --- a/.env.sample.mainnet +++ b/.env.sample.mainnet @@ -230,6 +230,21 @@ MEV_RELAYS=https://0xa15b52576bcbf1072f4a011c0f99f9fb6c66f3e1ff321f11f461d15e31b #EJECTOR_ALLOY_MONITORED=false #DV_EXIT_ALLOY_MONITORED=false +######### Optimum Gateway (opt-in) ######### + +# Adds getoptimum/gateway on the dvnode network. Off by default; Charon/VC/keys are unchanged. +# Docs: optimum/README.md +# 1. Set OPT_API_KEY and GATEWAY_CLUSTER_ID from Optimum onboarding (cluster id is not CLUSTER_NAME). +# 2. Append :compose-optimum.yml to COMPOSE_FILE above. Do not replace the existing list. +# 3. After the CL is up: ./optimum/init-optimum.sh && docker compose up -d optimum-gateway +#OPT_API_KEY= +#GATEWAY_CLUSTER_ID= +#GATEWAY_VERSION=v1.1.1 +#GATEWAY_PORT_LIBP2P=33212 +#GATEWAY_PORT_MUMP2P=43213 +#GATEWAY_PORT_TELEMETRY=48123 +#OPTIMUM_GATEWAY_ALLOY_MONITORED=false + ######### Debug Config ######### # This applies to compose-debug.yml only. diff --git a/.gitignore b/.gitignore index 03b84cb9..f20cc324 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ commit-boost/config.toml # Allow test fixtures !scripts/edit/**/test/fixtures/.charon/ !scripts/edit/**/test/fixtures/.charon/** + +# Optimum Gateway overlay (generated at runtime) +optimum/config/app_conf.yml +optimum/identity/ +optimum/cache/ diff --git a/CLAUDE.md b/CLAUDE.md index 41cdfea6..d95589ec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ Alternatives: The repository uses a **profile-based multi-client architecture** where different Ethereum client implementations can be swapped via `.env` configuration: -- **Compose file structure**: `compose-el.yml` (execution), `compose-cl.yml` (consensus), `compose-vc.yml` (validator), `compose-mev.yml` (MEV), and `docker-compose.yml` (main/monitoring) +- **Compose file structure**: `compose-el.yml` (execution), `compose-cl.yml` (consensus), `compose-vc.yml` (validator), `compose-mev.yml` (MEV), and `docker-compose.yml` (main/monitoring). Optional: `compose-debug.yml` (local debug stack), `compose-optimum.yml` (opt-in Optimum Gateway; off unless appended to `COMPOSE_FILE`, see `optimum/README.md`) - **Client selection**: Set via environment variables `EL`, `CL`, `VC`, `MEV` in `.env` (e.g., `EL=el-nethermind`, `CL=cl-lighthouse`, `VC=vc-lodestar`, `MEV=mev-mevboost`) - **Profiles**: Docker Compose profiles automatically activate the selected clients via `COMPOSE_PROFILES=${EL},${CL},${VC},${MEV}` - **Service naming**: Client services use prefixed names (e.g., `el-nethermind`, `cl-lighthouse`, `vc-lodestar`) while the main compose file uses unprefixed names for backward compatibility diff --git a/README.md b/README.md index 2566b15a..d9dbb7ca 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,12 @@ docker compose down cl-grandine docker compose up cl-grandine -d ``` +# Optimum Gateway (opt-in) + +Adds an [Optimum Gateway](https://getoptimum.github.io/optimum-gateway/versions/latest/) container on the `dvnode` network so the beacon node can receive blocks earlier via Optimum's mesh. Charon, the validator client, and DV keys are unchanged. + +Off by default. To enable, append `:compose-optimum.yml` to `COMPOSE_FILE` and follow [optimum/README.md](optimum/README.md). + # FAQs Check the Obol docs for frequent [errors and resolutions](https://docs.obol.org/docs/faq/errors) diff --git a/compose-optimum.yml b/compose-optimum.yml new file mode 100644 index 00000000..6f999e32 --- /dev/null +++ b/compose-optimum.yml @@ -0,0 +1,26 @@ +# Opt-in Optimum Gateway overlay. Not required for a stock CDVN. +# Append `:compose-optimum.yml` to COMPOSE_FILE in `.env` (see optimum/README.md). +# Adds one gateway service on the dvnode network; no CL, charon, or VC command changes. + +# Override any defaults specified by `${FOO:-bar}` in `.env` with `FOO=qux`. +# See .env.sample "Optimum Gateway (opt-in)" section. + +services: + optimum-gateway: + image: getoptimum/gateway:${GATEWAY_VERSION:-v1.1.1} + restart: unless-stopped + labels: + - "alloy-monitored=${OPTIMUM_GATEWAY_ALLOY_MONITORED:-false}" + ports: + - ${GATEWAY_PORT_LIBP2P:-33212}:33212 + - ${GATEWAY_PORT_MUMP2P:-43213}:43213 + - ${GATEWAY_PORT_TELEMETRY:-48123}:48123 + volumes: + - ./optimum/config:/app/config + - ./optimum/identity/libp2p:/tmp/libp2p + - ./optimum/identity/mump2p:/tmp/mump2p + - ./optimum/cache:/gateway/cache + environment: + OPT_API_KEY: ${OPT_API_KEY} + command: ["-config=/app/config/app_conf.yml"] + networks: [dvnode] diff --git a/optimum/.env.optimum.sample b/optimum/.env.optimum.sample new file mode 100644 index 00000000..7a669b9d --- /dev/null +++ b/optimum/.env.optimum.sample @@ -0,0 +1,20 @@ +# Copy the uncommented values into the CDVN `.env`. Do not replace COMPOSE_FILE; +# append `:compose-optimum.yml` to the existing list. +# +# API key and cluster id come from Optimum onboarding. The cluster id is not +# the Charon CLUSTER_NAME. + +#OPT_API_KEY=ogw_live_... +#GATEWAY_CLUSTER_ID= +#GATEWAY_VERSION=v1.1.1 + +# Then append to COMPOSE_FILE (keep the files already listed): +# COMPOSE_FILE=compose-el.yml:compose-cl.yml:compose-vc.yml:compose-mev.yml:docker-compose.yml:compose-optimum.yml + +# Optional port overrides (defaults shown): +# GATEWAY_PORT_LIBP2P=33212 +# GATEWAY_PORT_MUMP2P=43213 +# GATEWAY_PORT_TELEMETRY=48123 + +# Override CL P2P port on dvnode only if compose-cl.yml mapping changed: +# CL_P2P_INTERNAL_PORT=9000 diff --git a/optimum/README.md b/optimum/README.md new file mode 100644 index 00000000..f3eed365 --- /dev/null +++ b/optimum/README.md @@ -0,0 +1,72 @@ +# Optimum Gateway (opt-in) + +Adds an [Optimum Gateway](https://getoptimum.github.io/optimum-gateway/versions/latest/) container on the `dvnode` network so the consensus client can receive blocks earlier via Optimum's mesh. Charon, the validator client, and DV keys are unchanged. + +Off by default: a CDVN that does not append `compose-optimum.yml` to `COMPOSE_FILE` is unmodified. + +Partner API keys, `gateway_cluster_id`, and gateway config: [Gateway documentation](https://getoptimum.github.io/optimum-gateway/versions/latest/). + +## Prerequisites + +- Running CDVN with a beacon node (`CL` set to a `compose-cl.yml` service, e.g. `cl-lighthouse`). +- Optimum API key (`ogw_live_...`) and the `gateway_cluster_id` issued with that key. +- `curl` and `jq` on the host (for `init-optimum.sh`). + +## Enable + +From the CDVN root: + +1. Add Optimum variables to `.env` (see [`.env.optimum.sample`](.env.optimum.sample)): + + ```sh + OPT_API_KEY=ogw_live_... + GATEWAY_CLUSTER_ID=... # issued with the API key; not CLUSTER_NAME + GATEWAY_VERSION=v1.1.1 + ``` + +2. Append `:compose-optimum.yml` to the existing `COMPOSE_FILE` line. Do not replace the list. Example: + + ```sh + COMPOSE_FILE=compose-el.yml:compose-cl.yml:compose-vc.yml:compose-mev.yml:docker-compose.yml:compose-optimum.yml + ``` + +3. Start the stack so the CL is up, then write the gateway config and start the gateway: + + ```sh + docker compose up -d + ./optimum/init-optimum.sh + docker compose up -d optimum-gateway + ``` + +`init-optimum.sh` reads `CL`, `CL_PORT_HTTP`, and `GATEWAY_CLUSTER_ID` from `.env`, calls `GET /eth/v1/node/identity`, and writes `optimum/config/app_conf.yml`. + +No CL `command:` edits are required. The gateway is configured with the beacon node as a direct libp2p peer on `dvnode`. + +## Verify + +```sh +curl -s http://localhost:48123/health | jq '{status, checks: {cl_peers: .checks.cl_peers, mump2p_peers: .checks.mump2p_peers}}' + +# Optional: confirm the CL lists the gateway peer +GW_PEER=$(curl -s http://localhost:48123/api/v1/self_info | jq -r '.peer_id') +curl -s "http://127.0.0.1:${CL_PORT_HTTP:-5052}/eth/v1/node/peers/${GW_PEER}" | jq '.data | {state, direction}' +``` + +When connected: `checks.cl_peers >= 1`. + +## Disable + +Remove `:compose-optimum.yml` from `COMPOSE_FILE` and run `docker compose up -d`. Stock CDVN behaviour is restored. + +## Supported CL clients + +| `CL` in `.env` | P2P port on `dvnode` | +| -------------- | -------------------- | +| `cl-lighthouse` | 9000 | +| `cl-lodestar` | 9000 | +| `cl-teku` | 9000 | +| `cl-nimbus` | 9000 | +| `cl-grandine` | 9000 | +| `cl-prysm` | 13000 (host map is still `CL_PORT_P2P`, default 9000) | + +REST is `CL_PORT_HTTP` (default 5052) on all of the above. Set `CL` before running `init-optimum.sh`. Override the container P2P port with `CL_P2P_INTERNAL_PORT` only if you have changed `compose-cl.yml`. diff --git a/optimum/config/sample.app_conf.yml b/optimum/config/sample.app_conf.yml new file mode 100644 index 00000000..99280eee --- /dev/null +++ b/optimum/config/sample.app_conf.yml @@ -0,0 +1,15 @@ +log_level: info +# Must match the Optimum cluster issued with OPT_API_KEY (not the Charon cluster name). +gateway_cluster_id: REPLACE_GATEWAY_CLUSTER_ID + +agent_lib_p2p_port: 33212 +agent_mump2p_port: 43213 +telemetry_enable: true +telemetry_port: 48123 +identity_libp2p_dir: /tmp/libp2p +identity_mump2p_dir: /tmp/mump2p + +# Populated by init-optimum.sh. Port is the CL container P2P port on dvnode +# (9000 for most clients; 13000 for cl-prysm). +direct_cl_peers: + - /dns4/REPLACE_CL_HOST/tcp/REPLACE_CL_P2P_PORT/p2p/REPLACE_CL_PEER_ID diff --git a/optimum/init-optimum.sh b/optimum/init-optimum.sh new file mode 100755 index 00000000..400292da --- /dev/null +++ b/optimum/init-optimum.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Discover the CDVN CL peer id and write optimum/config/app_conf.yml. +# Run from the CDVN root after the CL beacon node is up. +# Internal P2P ports match compose-cl.yml (container port on the dvnode network, +# not the host-mapped CL_PORT_P2P). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CDVN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +[[ -f "${CDVN_ROOT}/.env" ]] && set -a && source "${CDVN_ROOT}/.env" && set +a + +CONFIG_DIR="${SCRIPT_DIR}/config" +SAMPLE="${CONFIG_DIR}/sample.app_conf.yml" +TARGET="${CONFIG_DIR}/app_conf.yml" + +CL="${CL:-}" +CL_REST_URL="${CL_REST_URL:-http://127.0.0.1:${CL_PORT_HTTP:-5052}}" +GATEWAY_CLUSTER_ID="${GATEWAY_CLUSTER_ID:-}" + +cl_p2p_port() { + case "${1}" in + cl-prysm) echo 13000 ;; + cl-lighthouse|cl-lodestar|cl-teku|cl-nimbus|cl-grandine) echo 9000 ;; + *) + echo "Unsupported CL='${1}'. Set CL in .env to a compose-cl.yml service (e.g. cl-lighthouse)." >&2 + return 1 + ;; + esac +} + +if [[ ! -f "${SAMPLE}" ]]; then + echo "Missing ${SAMPLE}" >&2 + exit 1 +fi + +if [[ -z "${CL}" ]]; then + echo "CL is unset. Source a CDVN .env (e.g. CL=cl-lighthouse) and retry." >&2 + exit 1 +fi + +if [[ -z "${GATEWAY_CLUSTER_ID}" ]]; then + echo "Set GATEWAY_CLUSTER_ID in .env to the Optimum cluster issued with your OPT_API_KEY." >&2 + echo "This is not the Charon cluster name. See optimum/README.md." >&2 + exit 1 +fi + +CL_P2P_INTERNAL_PORT="${CL_P2P_INTERNAL_PORT:-$(cl_p2p_port "${CL}")}" + +echo "Fetching CL peer id from ${CL_REST_URL}/eth/v1/node/identity ..." +PEER_ID="$(curl -sf "${CL_REST_URL}/eth/v1/node/identity" | jq -er '.data.peer_id')" +if [[ -z "${PEER_ID}" || "${PEER_ID}" == "null" ]]; then + echo "Failed to read CL peer id. Is the beacon node up and ${CL_REST_URL} reachable?" >&2 + exit 1 +fi + +mkdir -p "${SCRIPT_DIR}/identity/libp2p" "${SCRIPT_DIR}/identity/mump2p" "${SCRIPT_DIR}/cache" + +sed \ + -e "s|REPLACE_GATEWAY_CLUSTER_ID|${GATEWAY_CLUSTER_ID}|g" \ + -e "s|REPLACE_CL_HOST|${CL}|g" \ + -e "s|REPLACE_CL_P2P_PORT|${CL_P2P_INTERNAL_PORT}|g" \ + -e "s|REPLACE_CL_PEER_ID|${PEER_ID}|g" \ + "${SAMPLE}" > "${TARGET}" + +echo "Wrote ${TARGET}" +echo " gateway_cluster_id: ${GATEWAY_CLUSTER_ID}" +echo " direct_cl_peers: /dns4/${CL}/tcp/${CL_P2P_INTERNAL_PORT}/p2p/${PEER_ID}"