Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.sample.hoodi
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions .env.sample.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
26 changes: 26 additions & 0 deletions compose-optimum.yml
Original file line number Diff line number Diff line change
@@ -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]
20 changes: 20 additions & 0 deletions optimum/.env.optimum.sample
Original file line number Diff line number Diff line change
@@ -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
72 changes: 72 additions & 0 deletions optimum/README.md
Original file line number Diff line number Diff line change
@@ -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`.
15 changes: 15 additions & 0 deletions optimum/config/sample.app_conf.yml
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions optimum/init-optimum.sh
Original file line number Diff line number Diff line change
@@ -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}"