Skip to content
4 changes: 2 additions & 2 deletions charts/charon-cluster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Charon Cluster
===========

![Version: 0.3.6](https://img.shields.io/badge/Version-0.3.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.5.1](https://img.shields.io/badge/AppVersion-1.5.1-informational?style=flat-square)
![Version: 0.3.9](https://img.shields.io/badge/Version-0.3.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.7.0](https://img.shields.io/badge/AppVersion-1.7.0-informational?style=flat-square)

Charon is an open-source Ethereum Distributed validator middleware written in golang. This chart deploys a full Charon cluster.

Expand Down Expand Up @@ -52,7 +52,7 @@ Charon is an open-source Ethereum Distributed validator middleware written in go
| config.validatorApiAddress | string | `"0.0.0.0:3600"` | Listening address (ip and port) for validator-facing traffic proxying the beacon-node API. (default "127.0.0.1:3600") |
| containerSecurityContext | object | See `values.yaml` | The security context for containers |
| fullnameOverride | string | `""` | Provide a name to substitute for the full names of resources |
| image | object | `{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.5.1"}` | Charon image repository, pull policy, and tag version |
| image | object | `{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.7.0"}` | Charon image repository, pull policy, and tag version |
| imagePullSecrets | list | `[]` | Credentials to fetch images from private registry # ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ |
| livenessProbe | object | `{"enabled":false,"httpGet":{"path":"/livez"},"initialDelaySeconds":10,"periodSeconds":5}` | Configure liveness probes # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
| nameOverride | string | `""` | Provide a name in place of lighthouse for `app:` labels |
Expand Down
4 changes: 2 additions & 2 deletions charts/charon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Charon
===========

![Version: 0.4.6](https://img.shields.io/badge/Version-0.4.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.5.1](https://img.shields.io/badge/AppVersion-1.5.1-informational?style=flat-square)
![Version: 0.4.9](https://img.shields.io/badge/Version-0.4.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.7.0](https://img.shields.io/badge/AppVersion-1.7.0-informational?style=flat-square)

Charon is an open-source Ethereum Distributed validator middleware written in golang.

Expand Down Expand Up @@ -56,7 +56,7 @@ Charon is an open-source Ethereum Distributed validator middleware written in go
| extraVolumes | list | `[]` | Additional volumes |
| fullnameOverride | string | `""` | Provide a name to substitute for the full names of resources |
| httpPort | int | `3600` | HTTP Port |
| image | object | `{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.5.1"}` | Charon image repository, pull policy, and tag version |
| image | object | `{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.7.0"}` | Charon image repository, pull policy, and tag version |
| imagePullSecrets | list | `[]` | Credentials to fetch images from private registry # ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ |
| initContainers | list | `[]` | Additional init containers |
| jaegerPort | int | `6831` | Jaeger Port |
Expand Down
12 changes: 7 additions & 5 deletions charts/dv-pod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ A Helm chart for deploying a single distributed validator pod with Charon middle
| charon.beaconNodeHeadersSecretName | string | `""` | Optional: Name of the secret containing beacon node headers Use this for secure credential storage instead of beaconNodeHeaders The secret should contain a key specified by beaconNodeHeadersSecretKey |
| charon.builderApi | string | `""` | Enables the builder api. Will only produce builder blocks. Builder API must also be enabled on the validator client. Beacon node must be connected to a builder-relay to access the builder network. |
| charon.directConnectionEnabled | string | `"true"` | If enabled, it will set p2pExternalHostname value to the pod name and enable direct connection between cluster nodes. This is useful for deployments where pods can directly communicate with each other. |
| charon.dkgSidecar | object | `{"apiEndpoint":"https://api.obol.tech","enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"ghcr.io/obolnetwork/charon-dkg-sidecar","tag":"v1.0.0"},"initialRetryDelaySeconds":10,"maxRetryDelaySeconds":300,"pageLimit":10,"resources":{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}},"retryDelayFactor":2,"retryDelaySeconds":10,"serviceAccount":{"create":true}}` | Configuration for the DKG Sidecar init container This init container orchestrates the Distributed Key Generation (DKG) process for Charon clusters. The sidecar has three operating modes: 1. If cluster-lock.json exists: Exits immediately (cluster already initialized) 2. If cluster-definition.json exists: Attempts DKG with the existing definition 3. If neither exists: Polls the Obol API for cluster invites and runs DKG when ready To provide a pre-existing cluster-lock and skip DKG: 1. Create a configMap: kubectl create configmap cluster-lock --from-file=cluster-lock.json 2. The sidecar will detect the lock file and exit, allowing Charon to start immediately To provide a cluster-definition without running DKG through the API: 1. Mount your cluster-definition.json in /charon-data/ 2. The sidecar will run 'charon dkg' to generate the cluster-lock.json Note: When providing a pre-existing cluster-lock.json, you must also ensure the associated validator keys are available in the charon-data volume. |
| charon.dkgSidecar | object | `{"apiEndpoint":"https://api.obol.tech","enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"ghcr.io/obolnetwork/charon-dkg-sidecar","tag":"v1.0.0"},"initialRetryDelaySeconds":10,"maxRetryDelaySeconds":300,"pageLimit":10,"resources":{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}},"retryDelayFactor":2,"retryDelaySeconds":10,"serviceAccount":{"create":true},"targetConfigHash":""}` | Configuration for the DKG Sidecar init container This init container orchestrates the Distributed Key Generation (DKG) process for Charon clusters. The sidecar has three operating modes: 1. If cluster-lock.json exists: Exits immediately (cluster already initialized) 2. If cluster-definition.json exists: Attempts DKG with the existing definition 3. If neither exists: Polls the Obol API for cluster invites and runs DKG when ready To provide a pre-existing cluster-lock and skip DKG: 1. Create a configMap: kubectl create configmap cluster-lock --from-file=cluster-lock.json 2. The sidecar will detect the lock file and exit, allowing Charon to start immediately To provide a cluster-definition without running DKG through the API: 1. Mount your cluster-definition.json in /charon-data/ 2. The sidecar will run 'charon dkg' to generate the cluster-lock.json Note: When providing a pre-existing cluster-lock.json, you must also ensure the associated validator keys are available in the charon-data volume. |
| charon.dkgSidecar.apiEndpoint | string | `"https://api.obol.tech"` | API endpoint for the Obol network to fetch cluster definitions |
| charon.dkgSidecar.image.repository | string | `"ghcr.io/obolnetwork/charon-dkg-sidecar"` | Image repository for the DKG sidecar |
| charon.dkgSidecar.initialRetryDelaySeconds | int | `10` | Initial delay in seconds before the first retry of a polling cycle. |
Expand All @@ -38,9 +38,10 @@ A Helm chart for deploying a single distributed validator pod with Charon middle
| charon.dkgSidecar.retryDelayFactor | int | `2` | Factor by which the retry delay increases after each polling cycle (e.g., 2 for doubling). |
| charon.dkgSidecar.retryDelaySeconds | int | `10` | Delay in seconds between polling retries |
| charon.dkgSidecar.serviceAccount | object | `{"create":true}` | Service account settings for test pods |
| charon.dkgSidecar.targetConfigHash | string | `""` | Target cluster definition config hash for the DKG sidecar to partake in (optional). Must be a valid 32-byte hex hash with 0x prefix (66 characters total). Example: "0x7f0fd29abb11674b4e61000de26bff3600237aab0402427bd1409756665c2115" |
| charon.enr.existingSecret | object | `{"dataKey":"private-key","name":""}` | Point to an existing Kubernetes secret that holds the ENR private key. If 'privateKey' above is not set and this 'existingSecret.name' is provided, 'generate' is ignored. NOTE: If not set, the chart will automatically check for a secret named 'charon-enr-private-key' (configurable via secrets.defaultEnrPrivateKey) and use it if it exists. |
| charon.enr.generate | object | `{"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.5.1"},"kubectlImage":{"pullPolicy":"IfNotPresent","repository":"bitnami/kubectl","tag":"1.33.3"}}` | Enable automatic generation of an ENR private key. This is active only if 'privateKey' and 'existingSecret.name' are NOT set. The generated key will be stored in a secret (e.g., "{{ .Release.Name }}-dv-pod-enr-key") with data keys 'private-key' (for the hex key) and 'public-enr' (for the ENR string). |
| charon.enr.generate.kubectlImage | object | `{"pullPolicy":"IfNotPresent","repository":"bitnami/kubectl","tag":"1.33.3"}` | Image to use for kubectl operations within the ENR generation job This image must contain a compatible kubectl binary. |
| charon.enr.generate | object | `{"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.5.1"},"kubectlImage":{"pullPolicy":"IfNotPresent","repository":"alpine/k8s","tag":"1.31.4"}}` | Enable automatic generation of an ENR private key. This is active only if 'privateKey' and 'existingSecret.name' are NOT set. The generated key will be stored in a secret (e.g., "{{ .Release.Name }}-dv-pod-enr-key") with data keys 'private-key' (for the hex key) and 'public-enr' (for the ENR string). |
| charon.enr.generate.kubectlImage | object | `{"pullPolicy":"IfNotPresent","repository":"alpine/k8s","tag":"1.31.4"}` | Image to use for kubectl operations within the ENR generation job This image must contain a compatible kubectl binary and shell. |
| charon.enr.privateKey | string | `""` | Provide the ENR private key directly (hex format, e.g., 0x...). If set, 'generate' and 'existingSecret' are ignored. |
| charon.enrJob.enabled | bool | `true` | Enable or disable the Kubernetes Job that generates/manages the ENR. Note: This is typically not needed as the job automatically detects existing secrets. The job will check if the ENR secret already exists and skip generation if found. Only set to false for advanced use cases where you need to completely disable the job. |
| charon.executionClientRpcEndpoint | string | `""` | Execution client RPC endpoint URL (e.g., your Ethereum execution client) Note: Charon currently only supports a single execution endpoint |
Expand Down Expand Up @@ -132,10 +133,11 @@ A Helm chart for deploying a single distributed validator pod with Charon middle
| serviceMonitor.scheme | string | `"http"` | ServiceMonitor scheme |
| serviceMonitor.scrapeTimeout | string | `"30s"` | ServiceMonitor scrape timeout |
| serviceMonitor.tlsConfig | object | `{}` | ServiceMonitor TLS configuration |
| tests | object | `{"dkgSidecar":{"enabled":true,"hostNetwork":false,"mockApi":{"image":{"pullPolicy":"Always"},"port":3001},"operatorAddress":"0x3D1f0598943239806A251899016EAf4920d4726d","serviceAccount":{"create":true}}}` | Configuration for running Helm tests. These values are typically only used when `helm test` is run. |
| tests.dkgSidecar | object | `{"enabled":true,"hostNetwork":false,"mockApi":{"image":{"pullPolicy":"Always"},"port":3001},"operatorAddress":"0x3D1f0598943239806A251899016EAf4920d4726d","serviceAccount":{"create":true}}` | The operator address to use for DKG sidecar tests. This should be a valid Ethereum address (0x...). |
| tests | object | `{"dkgSidecar":{"enabled":true,"hostNetwork":false,"mockApi":{"image":{"pullPolicy":"Always"},"port":3001},"operatorAddress":"0x3D1f0598943239806A251899016EAf4920d4726d","serviceAccount":{"create":true},"targetConfigHash":"0x7f0fd29abb11674b4e61000de26bff3600237aab0402427bd1409756665c2115"}}` | Configuration for running Helm tests. These values are typically only used when `helm test` is run. |
| tests.dkgSidecar | object | `{"enabled":true,"hostNetwork":false,"mockApi":{"image":{"pullPolicy":"Always"},"port":3001},"operatorAddress":"0x3D1f0598943239806A251899016EAf4920d4726d","serviceAccount":{"create":true},"targetConfigHash":"0x7f0fd29abb11674b4e61000de26bff3600237aab0402427bd1409756665c2115"}` | The operator address to use for DKG sidecar tests. This should be a valid Ethereum address (0x...). |
| tests.dkgSidecar.hostNetwork | bool | `false` | Host network setting for dkgSidecar test pods |
| tests.dkgSidecar.serviceAccount | object | `{"create":true}` | Service account settings for test pods |
| tests.dkgSidecar.targetConfigHash | string | `"0x7f0fd29abb11674b4e61000de26bff3600237aab0402427bd1409756665c2115"` | Target config hash for testing (optional) When set, enables test-target-config-hash-set.yaml test and passes hash to DKG sidecar tests |
| tolerations | object | `{}` | Tolerations for pod assignment # ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ |
| updateStrategy | string | `"RollingUpdate"` | allows you to configure and disable automated rolling updates for containers, labels, resource request/limits, and annotations for the Pods in a StatefulSet. |
| validatorClient | object | `{"config":{"extraArgs":[],"graffiti":"DV-Pod"},"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"","tag":""},"keystores":{"autoCreate":true,"secretName":""},"resources":{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}},"type":"lighthouse"}` | Validator client configuration |
Expand Down
4 changes: 4 additions & 0 deletions charts/dv-pod/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ spec:
value: "/charon-data/charon-enr-private-key"
- name: CHAIN_ID
value: "{{ .Values.chainId }}"
{{- if .Values.charon.dkgSidecar.targetConfigHash }}
- name: TARGET_CONFIG_HASH
value: "{{ .Values.charon.dkgSidecar.targetConfigHash }}"
{{- end }}
volumeMounts:
- name: enr-data
mountPath: /enr-from-job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- if .Values.tests.dkgSidecar.targetConfigHash }}
- name: TARGET_CONFIG_HASH
value: "{{ .Values.tests.dkgSidecar.targetConfigHash }}"
{{- end }}
volumeMounts:
- name: charon-data
mountPath: /charon-data
Expand Down
59 changes: 59 additions & 0 deletions charts/dv-pod/templates/tests/test-target-config-hash-set.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{{- if and .Values.tests.dkgSidecar.enabled .Values.tests.dkgSidecar.targetConfigHash }}
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-target-config-hash-test"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": hook-succeeded
"helm.sh/hook-weight": "1"
labels:
app.kubernetes.io/name: {{ template "dv-pod.name" . }}
helm.sh/chart: {{ template "dv-pod.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/component: test
test-scenario: target-config-hash
spec:
restartPolicy: Never
containers:
- name: verify-target-config-hash
image: busybox
command:
- sh
- -c
- |
echo "========================================"
echo "Testing TARGET_CONFIG_HASH Environment Variable"
echo "========================================"
echo ""
echo "Expected hash: {{ .Values.tests.dkgSidecar.targetConfigHash }}"
echo "Actual TARGET_CONFIG_HASH: ${TARGET_CONFIG_HASH:-<NOT SET>}"
echo ""

if [ -z "$TARGET_CONFIG_HASH" ]; then
echo "❌ FAIL: TARGET_CONFIG_HASH environment variable is not set"
exit 1
fi

EXPECTED_HASH="{{ .Values.tests.dkgSidecar.targetConfigHash }}"

if [ "$TARGET_CONFIG_HASH" = "$EXPECTED_HASH" ]; then
echo "✅ PASS: TARGET_CONFIG_HASH matches expected value"
echo ""
echo "Hash format validation:"
echo " - Starts with 0x: $(echo $TARGET_CONFIG_HASH | grep -q '^0x' && echo 'YES' || echo 'NO')"
echo " - Length: $(echo -n $TARGET_CONFIG_HASH | wc -c) characters (expected: 66)"
echo ""
exit 0
else
echo "❌ FAIL: TARGET_CONFIG_HASH does not match expected value"
echo " Expected: $EXPECTED_HASH"
echo " Got: $TARGET_CONFIG_HASH"
exit 1
fi
env:
- name: TARGET_CONFIG_HASH
value: "{{ .Values.tests.dkgSidecar.targetConfigHash }}"
{{- end }}
12 changes: 12 additions & 0 deletions charts/dv-pod/templates/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@
{{- fail ".Values.centralMonitoring.token is required" }}
{{- end }}
{{- end }}

{{- if .Values.charon.dkgSidecar.targetConfigHash }}
{{- if not (hasPrefix "0x" .Values.charon.dkgSidecar.targetConfigHash) }}
{{- fail "charon.dkgSidecar.targetConfigHash must start with '0x'" }}
{{- end }}
{{- if ne (len .Values.charon.dkgSidecar.targetConfigHash) 66 }}
{{- fail "charon.dkgSidecar.targetConfigHash must be exactly 66 characters (0x + 64 hex digits)" }}
{{- end }}
{{- if not (regexMatch "^0x[0-9a-fA-F]{64}$" .Values.charon.dkgSidecar.targetConfigHash) }}
{{- fail "charon.dkgSidecar.targetConfigHash must be a valid hex hash (0x followed by 64 hexadecimal characters)" }}
{{- end }}
{{- end }}
45 changes: 45 additions & 0 deletions charts/dv-pod/values-examples/with-target-config-hash.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Example: Using targetConfigHash to participate in a specific cluster
#
# The targetConfigHash parameter allows your DKG sidecar to automatically join a specific
# cluster definition when polling the Obol API. This is useful when:
#
# 1. Multiple clusters are available for your operator address
# 2. You want to ensure the sidecar joins the correct cluster
# 3. You're coordinating with other operators on a specific cluster configuration
#
# How to get your targetConfigHash:
# - Visit https://launchpad.obol.tech
# - Create or view your cluster definition
# - Copy the config hash from the cluster details
#
# Format Requirements:
# - Must start with "0x"
# - Must be exactly 66 characters total (0x + 64 hex digits)
# - Represents a 32-byte Keccak256 hash
#
# Example deployment:
# helm upgrade --install my-dv-pod obol/dv-pod \
# --set charon.dkgSidecar.targetConfigHash="0x7f0fd29abb11674b4e61000de26bff3600237aab0402427bd1409756665c2115" \
# --set charon.operatorAddress="0xYourOperatorAddress" \
# -n dv-namespace --create-namespace

# Standard Charon configuration
charon:
operatorAddress: "0x0000000000000000000000000000000000000000" # Replace with your operator address
beaconNodeEndpoints:
- "http://your-beacon-node:5052"

# DKG Sidecar configuration with target config hash
dkgSidecar:
enabled: true
# Specify the exact cluster definition to join
targetConfigHash: "0x7f0fd29abb11674b4e61000de26bff3600237aab0402427bd1409756665c2115"
apiEndpoint: "https://api.obol.tech"

# Adjust polling intervals if needed
initialRetryDelaySeconds: 10
maxRetryDelaySeconds: 300
retryDelayFactor: 2

# Other standard configurations
chainId: "11155111" # Sepolia
Loading