diff --git a/README.md b/README.md index 06abe9ad..d16ef05f 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,11 @@ This repo contains Helm Charts for deploying Obol Distributed Validator [middlew ## List of charts -- [`charon`](charts/charon) -- [`charon-cluster`](charts/charon-cluster) -- [`charon-relay`](charts/charon-relay) +- [`charon`](charts/charon) - A chart for running a single charon container. +- [`charon-cluster`](charts/charon-cluster) - A chart for running a number of charon instances. +- [`charon-relay`](charts/charon-relay) - A chart for running a charon [relay](https://docs.obol.org/learn/charon/charon-cli-reference#host-a-relay). +- [`dv-pod`](charts/dv-pod) - A chart for running a Charon client + a Validator client, with automatic DKG completion as a feature. +- [`obol-app`](charts/obol-app) - A chart for running arbitrary docker images in the [Obol Stack](https://obol.org/stack). ## Before you begin diff --git a/charts/dv-pod/Chart.yaml b/charts/dv-pod/Chart.yaml index f2b7bca8..a573d87d 100644 --- a/charts/dv-pod/Chart.yaml +++ b/charts/dv-pod/Chart.yaml @@ -1,12 +1,12 @@ apiVersion: v2 name: dv-pod -description: A Helm chart for deploying a single distributed validator pod with Charon middleware and validator client +description: A Helm chart for deploying a single distributed validator pod consisting of a Charon middleware client and validator client. icon: https://raw.githubusercontent.com/ObolNetwork/charon/main/docs/images/charonlogo.svg -home: https://obol.tech/ +home: https://docs.obol.org/ sources: - https://github.com/ObolNetwork/charon maintainers: - - name: Eth2DevOps + - name: DV Labs email: devops@obol.tech keywords: - ethereum @@ -31,7 +31,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.2.0 +version: 0.3.0 # This is the version number of the application being deployed. This version number should be diff --git a/charts/dv-pod/README.md b/charts/dv-pod/README.md index 3ae3a2ca..071f09f3 100644 --- a/charts/dv-pod/README.md +++ b/charts/dv-pod/README.md @@ -2,156 +2,16 @@ Charon Cluster =========== -![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-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.0](https://img.shields.io/badge/Version-0.3.0-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) -A Helm chart for deploying a single distributed validator pod with Charon middleware and validator client +A Helm chart for deploying a single distributed validator pod consisting of a Charon middleware client and validator client. -**Homepage:** +**Homepage:** ## Source Code * -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | object | `{}` | Affinity for pod assignment # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity # # Example: # affinity: # podAntiAffinity: # requiredDuringSchedulingIgnoredDuringExecution: # - labelSelector: # matchExpressions: # - key: app.kubernetes.io/name # operator: In # values: # - charon # topologyKey: kubernetes.io/hostname # | -| centralMonitoring | object | `{"enabled":false,"promEndpoint":"https://vm.monitoring.gcp.obol.tech/write","token":""}` | Central Monitoring | -| centralMonitoring.enabled | bool | `false` | Specifies whether central monitoring should be enabled | -| centralMonitoring.promEndpoint | string | `"https://vm.monitoring.gcp.obol.tech/write"` | https endpoint to obol central prometheus | -| centralMonitoring.token | string | `""` | The authentication token to the central prometheus | -| chainId | int | `560048` | Chain ID for the network (1: Mainnet, 100: Gnosis, 560048: Hoodi) | -| charon.beaconNodeEndpoints | list | `["http://beacon-node:5052"]` | Beacon node endpoints (used when sub-charts are disabled) These will be used by both Charon and the validator client | -| charon.beaconNodeHeaders | string | `""` | Beacon node authentication headers WARNING: These headers will be sent to ALL beacon nodes, which could leak credentials Format: "Authorization=Basic " To generate: echo -n "username:password" | base64 Example: "Authorization=Basic am9objpkb2U=" | -| charon.beaconNodeHeadersSecretKey | string | `"headers"` | Key within the beacon node headers secret | -| 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},"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. | -| charon.dkgSidecar.maxRetryDelaySeconds | int | `300` | Maximum delay in seconds for exponential backoff between polling cycles. | -| charon.dkgSidecar.pageLimit | int | `10` | Page limit for API calls when fetching cluster definitions | -| charon.dkgSidecar.resources | object | `{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}}` | Resources for the cluster poller init container | -| 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":"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 | -| charon.fallbackBeaconNodeEndpoints | list | `[]` | Fallback beacon node endpoints (optional) These will be used if the primary beaconNodeEndpoints are unavailable | -| charon.featureSet | string | `"stable"` | Minimum feature set to enable by default: alpha, beta, or stable. Warning: modify at own risk. (default "stable") | -| charon.featureSetDisable | string | `""` | Comma-separated list of features to disable, overriding the default minimum feature set. | -| charon.featureSetEnable | string | `""` | Comma-separated list of features to enable, overriding the default minimum feature set. | -| charon.lockFile | string | `"/charon-data/cluster-lock.json"` | The path to the cluster lock file defining distributed validator cluster. (default ".charon/cluster-lock.json") | -| charon.logFormat | string | `"json"` | Log format; console, logfmt or json (default "console") | -| charon.logLevel | string | `"info"` | Log level; debug, info, warn or error (default "info") | -| charon.lokiAddresses | string | `""` | Enables sending of logfmt structured logs to these Loki log aggregation server addresses. This is in addition to normal stderr logs. | -| charon.lokiService | string | `""` | Service label sent with logs to Loki. | -| charon.monitoringAddress | string | `"0.0.0.0:3620"` | Listening address (ip and port) for the monitoring API (prometheus, pprof). (default "127.0.0.1:3620") | -| charon.noVerify | bool | `false` | Disables cluster definition and lock file verification. | -| charon.operatorAddress | string | `""` | The Ethereum address of this operator. This MUST be provided by the user. | -| charon.p2pAllowlist | string | `""` | Comma-separated list of CIDR subnets for allowing only certain peer connections. Example: 192.168.0.0/16 would permit connections to peers on your local network only. The default is to accept all connections. | -| charon.p2pDenylist | string | `""` | Comma-separated list of CIDR subnets for disallowing certain peer connections. Example: 192.168.0.0/16 would disallow connections to peers on your local network. The default is to accept all connections. | -| charon.p2pDisableReuseport | string | `""` | Disables TCP port reuse for outgoing libp2p connections. | -| charon.p2pExternalHostname | string | `""` | The DNS hostname advertised by libp2p. This may be used to advertise an external DNS. | -| charon.p2pExternalIp | string | `""` | The IP address advertised by libp2p. This may be used to advertise an external IP. | -| charon.p2pRelays | string | `""` | Comma-separated list of libp2p relay URLs or multiaddrs. Default list is provided by libp2p and may change over time | -| charon.p2pTcpAddress | string | `"0.0.0.0:3610"` | Comma-separated list of listening TCP addresses (ip and port) for libP2P traffic. Empty default doesn't bind to local port therefore only supports outgoing connections. | -| charon.privateKeyFile | string | `"/data/charon-enr-private-key"` | Path within the Charon container where the ENR private key file will be mounted. | -| charon.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") | -| configMaps | object | `{"clusterlock":""}` | Kubernetes configMaps names for non-sensitive configuration data. | -| configMaps.clusterlock | string | `""` | Name of the ConfigMap containing the cluster-lock.json file Set this to the name of an existing ConfigMap to skip the DKG process. Example: If you have created a ConfigMap named "my-cluster-lock": kubectl create configmap my-cluster-lock --from-file=cluster-lock.json Then set: clusterlock: "my-cluster-lock" If not set or if the ConfigMap doesn't exist, the DKG process will run. Note: ConfigMaps support larger file sizes than Secrets (up to 1MB compressed), making them more suitable for cluster-lock files which can be several megabytes. | -| 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 | -| 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 | -| networkPolicy | object | `{"beaconNodes":{"enabled":true,"ipBlock":{},"namespaceSelector":{},"podSelector":{},"port":null},"customEgress":[],"customIngress":[],"enabled":false,"monitoring":{"enabled":true,"namespaceSelector":{},"podSelector":{}},"obolApi":{"cidr":"0.0.0.0/0","enabled":true,"except":[]},"validatorClientNamespaceSelector":{},"validatorClientSelector":{}}` | NetworkPolicy configuration for pod network isolation | -| networkPolicy.beaconNodes | object | `{"enabled":true,"ipBlock":{},"namespaceSelector":{},"podSelector":{},"port":null}` | Beacon node configuration | -| networkPolicy.beaconNodes.enabled | bool | `true` | Enable egress to beacon nodes | -| networkPolicy.beaconNodes.ipBlock | object | `{}` | IP block for external beacon nodes | -| networkPolicy.beaconNodes.namespaceSelector | object | `{}` | Namespace selector for beacon nodes | -| networkPolicy.beaconNodes.podSelector | object | `{}` | Pod selector for beacon nodes | -| networkPolicy.beaconNodes.port | string | `nil` | Port for beacon node connections (leave empty for any port) | -| networkPolicy.customEgress | list | `[]` | Custom egress rules to add | -| networkPolicy.customIngress | list | `[]` | Custom ingress rules to add | -| networkPolicy.enabled | bool | `false` | Enable NetworkPolicy to restrict network traffic | -| networkPolicy.monitoring | object | `{"enabled":true,"namespaceSelector":{},"podSelector":{}}` | Monitoring configuration | -| networkPolicy.monitoring.enabled | bool | `true` | Enable access from monitoring tools | -| networkPolicy.monitoring.namespaceSelector | object | `{}` | Namespace selector for monitoring namespace | -| networkPolicy.monitoring.podSelector | object | `{}` | Pod selector for monitoring tools (e.g., Prometheus) | -| networkPolicy.obolApi | object | `{"cidr":"0.0.0.0/0","enabled":true,"except":[]}` | Obol API configuration | -| networkPolicy.obolApi.cidr | string | `"0.0.0.0/0"` | CIDR block for Obol API access | -| networkPolicy.obolApi.enabled | bool | `true` | Enable egress to Obol API for DKG | -| networkPolicy.obolApi.except | list | `[]` | IP ranges to exclude | -| networkPolicy.validatorClientNamespaceSelector | object | `{}` | Namespace selector for validator client access | -| networkPolicy.validatorClientSelector | object | `{}` | Selector for validator client pods that can access the validator API | -| nodeSelector | object | `{}` | Node labels for pod assignment # ref: https://kubernetes.io/docs/user-guide/node-selection/ | -| persistence | object | `{"accessModes":["ReadWriteOnce"],"enabled":true,"size":"1Gi","validatorDataSize":"500Mi"}` | Persistence configuration for DKG artifacts and charon data | -| persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes for the PVC. Must be a list. Default: ["ReadWriteOnce"]. | -| persistence.enabled | bool | `true` | Enable persistence using a PersistentVolumeClaim. | -| persistence.size | string | `"1Gi"` | Size of the PVC for charon-data. | -| persistence.validatorDataSize | string | `"500Mi"` | Size of the PVC for validator-data. Validator data includes slashing protection DB and other validator state. NOTE: Validator data ALWAYS uses persistent storage to prevent slashing, even if persistence.enabled is false for charon-data. | -| podAnnotations | object | `{"pod-security.kubernetes.io/audit":"baseline","pod-security.kubernetes.io/audit-version":"latest","pod-security.kubernetes.io/enforce":"baseline","pod-security.kubernetes.io/enforce-version":"latest","pod-security.kubernetes.io/warn":"baseline","pod-security.kubernetes.io/warn-version":"latest"}` | Pod annotations | -| podDisruptionBudget | object | `{"enabled":true,"minAvailable":""}` | Enable pod disruption budget # ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb | -| priorityClassName | string | `""` | Used to assign priority to pods # ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ | -| rbac | object | `{"clusterRules":[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","watch"]}],"enabled":true,"name":"","rules":[{"apiGroups":[""],"resources":["services"],"verbs":["get","list","watch"]}]}` | RBAC configuration. # ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ | -| rbac.clusterRules | list | `[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","watch"]}]` | Required ClusterRole rules | -| rbac.clusterRules[0] | object | `{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","watch"]}` | Required to obtain the nodes external IP | -| rbac.enabled | bool | `true` | Specifies whether RBAC resources are to be created | -| rbac.name | string | `""` | The name of the cluster role to use. If not set and create is true, a name is generated using the fullname template | -| rbac.rules | list | `[{"apiGroups":[""],"resources":["services"],"verbs":["get","list","watch"]}]` | Required Role rules | -| rbac.rules[0] | object | `{"apiGroups":[""],"resources":["services"],"verbs":["get","list","watch"]}` | Required to get information about the serices nodePort. | -| readinessProbe | object | `{"enabled":false,"httpGet":{"path":"/readyz"},"initialDelaySeconds":5,"periodSeconds":3}` | Configure readiness probes # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ | -| resources | object | `{"limits":{"cpu":"2000m","memory":"4Gi"},"requests":{"cpu":"1000m","memory":"2Gi"}}` | Pod resources limits and requests | -| secrets | object | `{"defaultEnrPrivateKey":"charon-enr-private-key","enrPrivateKey":"charon-enr-private-key"}` | Kubernetes secrets names that might be used as suffixes or for other purposes. For the ENR, the secret name is either defined in 'charon.enr.existingSecret.name' or generated by the ENR job (e.g., {{ .Release.Name }}-dv-pod-enr-key). | -| secrets.defaultEnrPrivateKey | string | `"charon-enr-private-key"` | Default ENR private key secret name to check for auto-detection IMPORTANT: This secret MUST exist in the same namespace as the Helm release If this secret exists in the release namespace, it will be used automatically unless explicitly overridden by charon.enr.existingSecret.name The ENR job will NOT check other namespaces to prevent unexpected behavior | -| secrets.enrPrivateKey | string | `"charon-enr-private-key"` | Suffix for ENR private key secret (used internally by templates) | -| securityContext | object | See `values.yaml` | The security context for pods Note: This must be set to null or omit runAsNonRoot to allow the prepare-validator-data init container to run as root for setting file permissions | -| service | object | `{"clusterIP":"None","ports":{"monitoring":{"name":"monitoring","port":3620,"protocol":"TCP","targetPort":3620},"p2pTcp":{"name":"p2p-tcp","port":3610,"protocol":"TCP","targetPort":3610},"validatorApi":{"name":"validator-api","port":3600,"protocol":"TCP","targetPort":3600}}}` | Charon service ports | -| service.clusterIP | string | `"None"` | Headless service to create DNS for each statefulset instance | -| serviceAccount | object | `{"annotations":{},"enabled":true,"name":""}` | Service account # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ | -| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | -| serviceAccount.enabled | bool | `true` | Specifies whether a service account should be created | -| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the default template | -| serviceMonitor | object | `{"annotations":{},"enabled":false,"interval":"1m","labels":{},"namespace":null,"path":"/metrics","relabelings":[],"scheme":"http","scrapeTimeout":"30s","tlsConfig":{}}` | Prometheus Service Monitor # ref: https://github.com/coreos/prometheus-operator | -| serviceMonitor.annotations | object | `{}` | Additional ServiceMonitor annotations | -| serviceMonitor.enabled | bool | `false` | If true, a ServiceMonitor CRD is created for a prometheus operator. https://github.com/coreos/prometheus-operator TODO: SWITCH BACK TO ON FOR PRODUCTION | -| serviceMonitor.interval | string | `"1m"` | ServiceMonitor scrape interval | -| serviceMonitor.labels | object | `{}` | Additional ServiceMonitor labels | -| serviceMonitor.namespace | string | `nil` | Alternative namespace for ServiceMonitor | -| serviceMonitor.path | string | `"/metrics"` | Path to scrape | -| serviceMonitor.relabelings | list | `[]` | ServiceMonitor relabelings | -| 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},"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 | -| validatorClient.config | object | `{"extraArgs":[],"graffiti":"DV-Pod"}` | Validator client specific configuration | -| validatorClient.config.extraArgs | list | `[]` | Additional CLI arguments for the validator client | -| validatorClient.config.graffiti | string | `"DV-Pod"` | Graffiti to include in proposed blocks | -| validatorClient.enabled | bool | `true` | Enable the validator client container | -| validatorClient.image | object | `{"pullPolicy":"IfNotPresent","repository":"","tag":""}` | Image configuration for validator client | -| validatorClient.keystores | object | `{"autoCreate":true,"secretName":""}` | Validator keystores configuration | -| validatorClient.keystores.autoCreate | bool | `true` | Automatically create secret from DKG-generated keystores Only applies when secretName is empty and DKG runs successfully | -| validatorClient.keystores.secretName | string | `""` | Name of the Secret containing validator keystores If provided, skip keystore generation and use existing keys The secret should contain keystore-*.json and keystore-*.txt files Example: kubectl create secret generic validator-keys --from-file=keystore-0.json --from-file=keystore-0.txt | -| validatorClient.resources | object | `{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}}` | Resource limits and requests for validator client | -| validatorClient.type | string | `"lighthouse"` | Type of validator client to use Options: lighthouse, teku, nimbus, lodestar | - # How to use this chart A distributed validator cluster is composed of the following containers: @@ -168,13 +28,13 @@ A distributed validator cluster is composed of the following containers: This chart deploys a single distributed validator pod. You have two options for providing the required artifacts: -### Option 1: Pre-existing DKG artifacts (skip DKG process) +### Option 1: Pre-created keys and artifacts (skips DKG process) -If you already have DKG artifacts from a ceremony, you can provide them to skip the DKG process entirely. +If you already have private keys and charon artifacts from a central key creation or a DKG ceremony, you can provide them to skip the automated DKG process entirely. -#### Example 1a: From a multi-node cluster output (./cluster/ structure) +#### Example 1a: From a `charon create cluster` command output (a `./cluster/nodeN` folder structure) -When you've run DKG for multiple nodes and have a `./cluster/` directory: +When you've created keystores for a full cluster locally and have a `./cluster/` directory: ``` ./cluster/ @@ -194,21 +54,26 @@ When you've run DKG for multiple nodes and have a `./cluster/` directory: ``` To deploy node0 using this chart: -```console -# Create ENR private key secret +```sh +# Put the ENR private key in a secret kubectl create secret generic charon-enr-private-key --from-file=cluster/node0/charon-enr-private-key +# Put the private keys in a kubernetes secret +kubectl create secret generic validator-keys \ + --from-file=cluster/node0/validator_keys/keystore-0.json --from-file=cluster/node0/validator_keys/keystore-0.txt + # Create the cluster lock ConfigMap (same for all nodes) -kubectl create configmap my-cluster-lock --from-file=cluster/node0/cluster-lock.json +kubectl create configmap cluster-lock --from-file=cluster/node0/cluster-lock.json -# Install the chart, referencing your ConfigMap +# Install the chart, referencing your ConfigMap and Secrets helm install my-dv-pod obol/dv-pod \ - --set configMaps.clusterlock=my-cluster-lock + --set configMaps.clusterLock=cluster-lock \ + --set validatorClient.keystores.secretName=validator-keys ``` -Note: The validator keys will be loaded from the persistent volume created during DKG. +That should be enough to get your DV-Pod running, and loaded with the required artifacts, assuming the default beacon node endpoint of the Obol Stack works. -#### Example 1b: From a single node output (.charon/ structure) +#### Example 1b: From a Distributed Key Generation ceremony output (a `.charon/` folder structure) When you have DKG artifacts in a `.charon/` directory: @@ -223,70 +88,27 @@ When you have DKG artifacts in a `.charon/` directory: Create the required resources: -> ⚠️ **IMPORTANT**: The ENR secret MUST be created in the same namespace where you plan to install the Helm chart. The ENR job will only look for secrets in its own namespace. - -```console -# Create namespace (if needed) -kubectl create namespace dv-pod - -# Create ENR private key secret IN THE SAME NAMESPACE +```sh +# Put the charon ENR private key in a kubernetes secret kubectl create secret generic charon-enr-private-key \ - --namespace dv-pod \ --from-file=.charon/charon-enr-private-key -# Create the cluster lock ConfigMap IN THE SAME NAMESPACE -kubectl create configmap my-cluster-lock \ - --namespace dv-pod \ +# Put the private keys in a kubernetes secret +kubectl create secret generic validator-keys \ + --from-file=.charon/validator_keys/keystore-0.json --from-file=.charon/validator_keys/keystore-0.txt + +# Put the cluster lock file in a kubernetes ConfigMap +kubectl create configmap cluster-lock \ --from-file=.charon/cluster-lock.json -# Install the chart IN THE SAME NAMESPACE +# Install the chart helm install my-dv-pod obol/dv-pod \ - --namespace dv-pod \ - --set configMaps.clusterlock=my-cluster-lock + --set configMaps.clusterLock=cluster-lock \ + --set validatorClient.keystores.secretName=validator-keys ``` -#### Handling Large Cluster-Lock Files (>1MB) - -If your cluster-lock.json file is larger than 1MB, you may encounter errors when creating the ConfigMap: - -```console -error validating data: ValidationError(ConfigMap.data.cluster-lock.json): invalid type for io.k8s.api.core.v1.ConfigMap.data: got "array", expected "string" -``` - -In this case, you have two options: - -**Option A: Direct lock hash (Recommended)** - -1. Extract the lock_hash from your cluster-lock.json: - ```console - LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) - echo $LOCK_HASH - ``` - -2. Install the chart with the lockHash value: - ```console - helm install my-dv-pod obol/dv-pod \ - --set charon.lockHash=$LOCK_HASH \ - --set charon.operatorAddress= - ``` - -**Option B: ConfigMap approach** - -1. Extract the lock_hash and create a ConfigMap: - ```console - LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) - kubectl create configmap cluster-lock-hash \ - --from-literal=lock-hash=$LOCK_HASH - ``` - -2. Install the chart referencing the ConfigMap: - ```console - helm install my-dv-pod obol/dv-pod \ - --set configMaps.lockHash=cluster-lock-hash \ - --set charon.operatorAddress= - ``` - -The DKG sidecar will use the lock hash to fetch the full cluster lock from the Obol API. +> [!IMPORTANT] +> The ENR secret MUST be created in the same namespace where you plan to install the Helm chart. The ENR job can only look for secrets in its own namespace. ### Option 2: Run DKG through the chart (automatic) @@ -296,7 +118,7 @@ If you don't have pre-existing artifacts, the chart can automatically: 3. Store the resulting artifacts in a persistent volume Simply deploy the chart with your operator address: -```console +```sh helm install my-dv-pod obol/dv-pod \ --set charon.operatorAddress=0xYOUR_OPERATOR_ADDRESS ``` @@ -316,9 +138,7 @@ Install a distributed validator pod: ```sh helm upgrade --install my-dv-pod obol/dv-pod \ --set='charon.beaconNodeEndpoints[0]=' \ - --set='charon.operatorAddress=' \ - --create-namespace \ - --namespace dv-pod + --set='charon.operatorAddress=' ``` ## Validator Client @@ -354,7 +174,7 @@ The chart supports two methods for providing validator keystores: If you have existing keystores, create a Kubernetes secret and reference it: -```console +```sh # Create secret with your keystores kubectl create secret generic validator-keys \ --from-file=keystore-0.json \ @@ -365,7 +185,7 @@ kubectl create secret generic validator-keys \ # Deploy the chart with the keystore secret helm install my-dv-pod obol/dv-pod \ --set validatorClient.keystores.secretName=validator-keys \ - --set configMaps.clusterlock=my-cluster-lock + --set configMaps.clusterLock=my-cluster-lock ``` #### Option 2: DKG-Generated Keystores (Automatic) @@ -422,9 +242,206 @@ For example, if you installed the chart as `my-dv-pod` in namespace `dv-pod`: Note: The Charon validator API on port 3600 provides the same interface as a beacon node API, allowing standard validator clients to connect without modification. +### Handling Large Cluster-Lock Files (>1MB) + +If your `cluster-lock.json` file is larger than 1MB, you may encounter errors when creating the ConfigMap: + +```sh +error validating data: ValidationError(ConfigMap.data.cluster-lock.json): invalid type for io.k8s.api.core.v1.ConfigMap.data: got "array", expected "string" +``` + +In this case, you have two options: + +**Option A: Direct lock hash (Recommended)** + +1. Extract the lock_hash from your cluster-lock.json: + ```sh + LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) + echo $LOCK_HASH + ``` + +2. Install the chart with the lockHash value: + ```sh + helm install my-dv-pod obol/dv-pod \ + --set charon.lockHash=$LOCK_HASH \ + --set charon.operatorAddress= + ``` + +**Option B: ConfigMap approach** + +1. Extract the lock_hash and create a ConfigMap: + ```sh + LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) + kubectl create configmap cluster-lock-hash \ + --from-literal=lock-hash=$LOCK_HASH + ``` + +2. Install the chart referencing the ConfigMap: + ```sh + helm install my-dv-pod obol/dv-pod \ + --set configMaps.lockHash=cluster-lock-hash \ + --set charon.operatorAddress= + ``` + +The DKG sidecar will use the lock hash to fetch the full cluster lock from the Obol API. + ## Uninstall the Chart To uninstall and delete the `dv-pod` release: ```sh helm uninstall dv-pod -n dv-pod ``` The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Full Chart Values Reference + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Affinity for pod assignment # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity # # Example: # affinity: # podAntiAffinity: # requiredDuringSchedulingIgnoredDuringExecution: # - labelSelector: # matchExpressions: # - key: app.kubernetes.io/name # operator: In # values: # - charon # topologyKey: kubernetes.io/hostname # | +| centralMonitoring | object | `{"enabled":false,"promEndpoint":"https://vm.monitoring.gcp.obol.tech/write","token":""}` | Central Monitoring | +| centralMonitoring.enabled | bool | `false` | Specifies whether central monitoring should be enabled | +| centralMonitoring.promEndpoint | string | `"https://vm.monitoring.gcp.obol.tech/write"` | https endpoint to obol central prometheus | +| centralMonitoring.token | string | `""` | The authentication token to the central prometheus | +| chainId | int | `1` | Chain ID for the network (1: Mainnet, 11155111: Sepolia, 560048: Hoodi) Used for DKG Ceremony | +| charon.beaconNodeEndpoints | list | `["http://l1-full-node-beacon.l1.cluster.svc.local:5052"]` | Beacon node endpoints The default is the Obol Stack full node address. Change this to your own consensus client beacon API(s). All beacon nodes specified here will be called for every charon request, if you want to leverage fallback behaviour place secondary beacon nodes in charon.fallbackBeaconNodeEndpoints[] | +| charon.beaconNodeHeaders | string | `""` | Beacon node authentication headers WARNING: These headers will be sent to ALL beacon nodes, which could leak credentials Format: "Authorization=Basic " To generate: echo -n "username:password" | base64 Example: "Authorization=Basic am9objpkb2U=" | +| charon.beaconNodeHeadersSecretKey | string | `"headers"` | Optional: Key within the charon.beaconNodeHeaders secret to read from | +| 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 charon.beaconNodeHeadersSecretKey | +| charon.builderApi | string | `"true"` | Enables the builder api (MEV) for validator proposals. The Builder API must also be enabled on your beacon node client(s). The Beacon node must either be connected to a mev sidecar or a mev relay to access the builder network. More documentation about MEV can be found [here](https://docs.obol.org/advanced-and-troubleshooting/advanced/enable-mev). | +| 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":"obolnetwork/charon-dkg-sidecar","tag":"main"},"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. Change the `confiMaps.clusterLock: cluster-lock in your values.yaml file, or pass --set configMaps.clusterLock=cluster-lock in your helm install command. 3. The sidecar will detect the mounted 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 | `"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. | +| charon.dkgSidecar.maxRetryDelaySeconds | int | `300` | Maximum delay in seconds for exponential backoff between polling cycles. | +| charon.dkgSidecar.pageLimit | int | `10` | Page limit for API calls when fetching cluster definitions | +| charon.dkgSidecar.resources | object | `{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}}` | Resources for the cluster poller init container | +| 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 | `{"name":"","privateKeyDataKey":"charon-enr-private-key","publicKeyDataKey":"enr"}` | 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.existingSecret.privateKeyDataKey | string | `"charon-enr-private-key"` | Key in the secret's 'data' field holding the private key hex string | +| charon.enr.existingSecret.publicKeyDataKey | string | `"enr"` | Key in the secret's 'data' field holding the public ENR string | +| charon.enr.generate | object | `{"annotations":{},"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.6.1"},"kubectlImage":{"pullPolicy":"IfNotPresent","repository":"bitnamisecure/kubectl","tag":"latest"},"nodeSelector":{}}` | Enable automatic generation of an ENR private key. Only used if 'privateKey' and 'existingSecret.name' are not provided. The generated key will be stored in a secret with data keys 'charon-enr-private-key' and 'enr'. | +| charon.enr.generate.annotations | object | `{}` | Annotations to add to the ENR generation job | +| charon.enr.generate.kubectlImage | object | `{"pullPolicy":"IfNotPresent","repository":"bitnamisecure/kubectl","tag":"latest"}` | Image to use for kubectl operations within the ENR generation job This image must contain a compatible kubectl binary. | +| charon.enr.generate.nodeSelector | object | `{}` | Node selector for the ENR generation job | +| 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 | `""` | Optional: Execution client RPC endpoint URL (e.g., your Ethereum execution client) Note: Charon currently only supports a single execution endpoint This is only needed if an operator in the cluster uses a smart contract wallet for an operator signature. If that does not apply to this cluster, this field can be left unset. | +| charon.fallbackBeaconNodeEndpoints | list | `["https://ethereum-beacon-api.publicnode.com"]` | Fallback beacon node endpoints (optional) These will be used if the primary beaconNodeEndpoints are unavailable | +| charon.featureSet | string | `"stable"` | Minimum feature set to enable by default: alpha, beta, or stable. Warning: modify at own risk. (default "stable") | +| charon.featureSetDisable | string | `""` | Comma-separated list of features to disable, overriding the default minimum feature set. | +| charon.featureSetEnable | string | `""` | Comma-separated list of features to enable, overriding the default minimum feature set. | +| charon.lockFile | string | `"/charon-data/cluster-lock.json"` | The path on the pod to the cluster lock file that definesthe distributed validator cluster. (default "/charon-data/cluster-lock.json") | +| charon.lockHash | string | `""` | Cluster lock hash for large cluster-lock files (>1MB) When provided, the DKG sidecar will fetch the full cluster-lock from Obol API using this hash Extract the hash from a cluster-lock.json using: `jq -r '.lock_hash' cluster-lock.json` Alternative to providing the full cluster-lock.json file via the configMaps.clusterLock value | +| charon.logFormat | string | `"json"` | Log format; console, logfmt or json (default "json") | +| charon.logLevel | string | `"info"` | Log level; debug, info, warn or error (default "info") | +| charon.lokiAddresses | string | `""` | Enables sending of logfmt structured logs to these Loki log aggregation server addresses. This is in addition to normal stderr logs. | +| charon.lokiService | string | `""` | Service label sent with logs to Loki. | +| charon.monitoringAddress | string | `"0.0.0.0:3620"` | Listening address (ip and port) for the monitoring API (prometheus, pprof). (default "127.0.0.1:3620") | +| charon.nickname | string | `""` | The nickname this Charon appears as in monitoring and its peer's logs. Maximum 32 characters. | +| charon.noVerify | bool | `false` | Disables cluster definition and lock file verification. | +| charon.operatorAddress | string | `""` | The Ethereum address of this operator. This MUST be provided by the user to use the auto-dkg functionality. | +| charon.p2pAllowlist | string | `""` | Comma-separated list of CIDR subnets for allowing only certain peer connections. Example: 192.168.0.0/16 would permit connections to peers on your local network only. The default is to accept all connections. | +| charon.p2pDenylist | string | `""` | Comma-separated list of CIDR subnets for disallowing certain peer connections. Example: 192.168.0.0/16 would disallow connections to peers on your local network. The default is to accept all connections. | +| charon.p2pDisableReuseport | string | `""` | Disables TCP port reuse for outgoing libp2p connections. | +| charon.p2pExternalHostname | string | `""` | The DNS hostname advertised by libp2p. This may be used to advertise an external DNS. | +| charon.p2pExternalIp | string | `""` | The IP address advertised by libp2p. This may be used to advertise an external IP. | +| charon.p2pRelays | string | `""` | Comma-separated list of libp2p relay URLs or multiaddrs. Default list is provided by libp2p and may change over time | +| charon.p2pTcpAddress | string | `"0.0.0.0:3610"` | Comma-separated list of listening TCP addresses (ip and port) for libP2P traffic. Empty default doesn't bind to local port therefore only supports outgoing connections. | +| charon.privateKeyFile | string | `"/data/charon-enr-private-key"` | Path within the Charon container where the ENR private key file will be mounted. | +| charon.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") | +| clusterThreshold | string | `""` | Maximum number of pods that can be unavailable for cluster threshold Used in pod disruption budget when minAvailable is not specified | +| configMaps | object | `{"clusterLock":""}` | Kubernetes configMaps names for non-sensitive configuration data. | +| configMaps.clusterLock | string | `""` | Name of the ConfigMap containing the cluster-lock.json file Set this to the name of an existing ConfigMap to skip the DKG process. Example: kubectl create configmap cluster-lock --from-file=.charon/cluster-lock.json Then set: clusterLock: "cluster-lock" If not set or if the ConfigMap doesn't exist, the DKG process will run. NOTE: For large cluster-lock files (>1MB), use charon.lockHash instead clusterLock: "cluster-lock" | +| containerSecurityContext | object | See `values.yaml` | The security context for containers | +| fullnameOverride | string | `""` | Provide a name to substitute for the full names of resources | +| global | object | `{"annotations":{}}` | Global configuration that can be referenced across the chart Used for test configurations and shared settings | +| global.annotations | object | `{}` | Global annotations applied to resources | +| image | object | `{"pullPolicy":"IfNotPresent","repository":"obolnetwork/charon","tag":"v1.6.1"}` | 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","port":3620},"initialDelaySeconds":10,"periodSeconds":5}` | Configure liveness probes # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ | +| livenessProbe.httpGet.port | int | `3620` | Port for liveness probe HTTP checks | +| nameOverride | string | `""` | Provide a name in place of charon for `app:` labels | +| networkPolicy | object | `{"beaconNodes":{"enabled":true,"ipBlock":{},"namespaceSelector":{},"podSelector":{},"port":null},"customEgress":[],"customIngress":[],"enabled":false,"monitoring":{"enabled":true,"namespaceSelector":{},"podSelector":{}},"obolApi":{"cidr":"0.0.0.0/0","enabled":true,"except":[]},"validatorClientNamespaceSelector":{},"validatorClientSelector":{}}` | NetworkPolicy configuration for pod network isolation | +| networkPolicy.beaconNodes | object | `{"enabled":true,"ipBlock":{},"namespaceSelector":{},"podSelector":{},"port":null}` | Beacon node configuration | +| networkPolicy.beaconNodes.enabled | bool | `true` | Enable egress to beacon nodes | +| networkPolicy.beaconNodes.ipBlock | object | `{}` | IP block for external beacon nodes | +| networkPolicy.beaconNodes.namespaceSelector | object | `{}` | Namespace selector for beacon nodes | +| networkPolicy.beaconNodes.podSelector | object | `{}` | Pod selector for beacon nodes | +| networkPolicy.beaconNodes.port | string | `nil` | Port for beacon node connections (leave empty for any port) | +| networkPolicy.customEgress | list | `[]` | Custom egress rules to add | +| networkPolicy.customIngress | list | `[]` | Custom ingress rules to add | +| networkPolicy.enabled | bool | `false` | Enable NetworkPolicy to restrict network traffic | +| networkPolicy.monitoring | object | `{"enabled":true,"namespaceSelector":{},"podSelector":{}}` | Monitoring configuration | +| networkPolicy.monitoring.enabled | bool | `true` | Enable access from monitoring tools | +| networkPolicy.monitoring.namespaceSelector | object | `{}` | Namespace selector for monitoring namespace | +| networkPolicy.monitoring.podSelector | object | `{}` | Pod selector for monitoring tools (e.g., Prometheus) | +| networkPolicy.obolApi | object | `{"cidr":"0.0.0.0/0","enabled":true,"except":[]}` | Obol API configuration | +| networkPolicy.obolApi.cidr | string | `"0.0.0.0/0"` | CIDR block for Obol API access | +| networkPolicy.obolApi.enabled | bool | `true` | Enable egress to Obol API for DKG | +| networkPolicy.obolApi.except | list | `[]` | IP ranges to exclude | +| networkPolicy.validatorClientNamespaceSelector | object | `{}` | Namespace selector for validator client access | +| networkPolicy.validatorClientSelector | object | `{}` | Selector for validator client pods that can access the validator API | +| nodeSelector | object | `{}` | Node labels for pod assignment # ref: https://kubernetes.io/docs/user-guide/node-selection/ | +| persistence | object | `{"accessModes":["ReadWriteOnce"],"annotations":{},"enabled":true,"size":"1Gi","validatorDataSize":"500Mi"}` | Persistence configuration for DKG artifacts and charon data | +| persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes for the PVC. Must be a list. Default: ["ReadWriteOnce"]. | +| persistence.annotations | object | `{}` | Annotations for the PVC Example: annotations: {"volume.beta.kubernetes.io/storage-class": "fast"} | +| persistence.enabled | bool | `true` | Enable persistence using a PersistentVolumeClaim. | +| persistence.size | string | `"1Gi"` | Size of the PVC for charon-data. | +| persistence.validatorDataSize | string | `"500Mi"` | Size of the PVC for validator-data. Validator data includes slashing protection DB and other validator state. NOTE: Validator data ALWAYS uses persistent storage to prevent slashing, even if persistence.enabled is false for charon-data. | +| podAnnotations | object | `{"pod-security.kubernetes.io/audit":"baseline","pod-security.kubernetes.io/audit-version":"latest","pod-security.kubernetes.io/enforce":"baseline","pod-security.kubernetes.io/enforce-version":"latest","pod-security.kubernetes.io/warn":"baseline","pod-security.kubernetes.io/warn-version":"latest"}` | Pod annotations | +| podDisruptionBudget | object | `{"enabled":true,"minAvailable":""}` | Enable pod disruption budget # ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb | +| priorityClassName | string | `""` | Used to assign priority to pods # ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ | +| rbac | object | `{"clusterRules":[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","watch"]}],"enabled":true,"name":"","rules":[{"apiGroups":[""],"resources":["services"],"verbs":["get","list","watch"]}]}` | RBAC configuration. # ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ | +| rbac.clusterRules | list | `[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","watch"]}]` | Required ClusterRole rules | +| rbac.clusterRules[0] | object | `{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","watch"]}` | Required to obtain the nodes external IP | +| rbac.enabled | bool | `true` | Specifies whether RBAC resources are to be created | +| rbac.name | string | `""` | The name of the cluster role to use. If not set and create is true, a name is generated using the fullname template | +| rbac.rules | list | `[{"apiGroups":[""],"resources":["services"],"verbs":["get","list","watch"]}]` | Required Role rules | +| rbac.rules[0] | object | `{"apiGroups":[""],"resources":["services"],"verbs":["get","list","watch"]}` | Required to get information about the serices nodePort. | +| readinessProbe | object | `{"enabled":false,"httpGet":{"path":"/readyz","port":3620},"initialDelaySeconds":5,"periodSeconds":3}` | Configure readiness probes # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ | +| readinessProbe.httpGet.port | int | `3620` | Port for readiness probe HTTP checks | +| resources | object | `{"limits":{"cpu":"2000m","memory":"4Gi"},"requests":{"cpu":"1000m","memory":"2Gi"}}` | Pod resources limits and requests | +| secrets | object | `{"defaultEnrPrivateKey":"charon-enr-private-key"}` | Kubernetes secrets names For the ENR, the secret name is either defined in 'charon.enr.existingSecret.name' or generated by the ENR job (e.g., {{ .Release.Name }}-dv-pod-enr-key). | +| secrets.defaultEnrPrivateKey | string | `"charon-enr-private-key"` | Default ENR private key secret name to check for auto-detection IMPORTANT: This secret MUST exist in the same namespace as the Helm release If this secret exists in the release namespace, it will be used automatically unless explicitly overridden by charon.enr.existingSecret.name The ENR job will NOT check other namespaces to prevent unexpected behavior | +| securityContext | object | See `values.yaml` | The security context for pods Note: This must be set to null or omit runAsNonRoot to allow the prepare-validator-data init container to run as root for setting file permissions | +| service | object | `{"clusterIP":"None","ports":{"monitoring":{"name":"monitoring","port":3620,"protocol":"TCP","targetPort":3620},"p2pTcp":{"name":"p2p-tcp","port":3610,"protocol":"TCP","targetPort":3610},"validatorApi":{"name":"validator-api","port":3600,"protocol":"TCP","targetPort":3600}}}` | Charon service ports | +| service.clusterIP | string | `"None"` | Headless service to create DNS for each statefulset instance | +| serviceAccount | object | `{"annotations":{},"enabled":true,"name":"","nameTest":""}` | Service account # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.enabled | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set, a name is generated using the default template | +| serviceAccount.nameTest | string | `""` | The name of the service account to use for test pods. If not set, uses the main service account | +| serviceMonitor | object | `{"annotations":{},"enabled":false,"interval":"1m","labels":{},"namespace":null,"path":"/metrics","relabelings":[],"scheme":"http","scrapeTimeout":"30s","tlsConfig":{}}` | Prometheus Service Monitor # ref: https://github.com/coreos/prometheus-operator | +| serviceMonitor.annotations | object | `{}` | Additional ServiceMonitor annotations | +| serviceMonitor.enabled | bool | `false` | If true, a ServiceMonitor CRD is created for a prometheus operator. https://github.com/coreos/prometheus-operator TODO: SWITCH BACK TO ON FOR PRODUCTION | +| serviceMonitor.interval | string | `"1m"` | ServiceMonitor scrape interval | +| serviceMonitor.labels | object | `{}` | Additional ServiceMonitor labels | +| serviceMonitor.namespace | string | `nil` | Alternative namespace for ServiceMonitor | +| serviceMonitor.path | string | `"/metrics"` | Path to scrape | +| serviceMonitor.relabelings | list | `[]` | ServiceMonitor relabelings | +| 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},"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":"","network":""},"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"","tag":""},"keystores":{"secretName":""},"resources":{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}},"type":"lighthouse"}` | Validator client configuration | +| validatorClient.config | object | `{"extraArgs":[],"graffiti":"","network":""}` | Validator client specific configuration | +| validatorClient.config.extraArgs | list | `[]` | Additional CLI arguments for the validator client (the minimum required mev flags are automatically included when you set .Values.charon.builderApi to true) | +| validatorClient.config.graffiti | string | `""` | Graffiti to include in proposed blocks Leaving it unset will result in charon defaults. | +| validatorClient.config.network | string | `""` | Network configuration for validator client (e.g., mainnet, sepolia) Used by some validator clients like Teku for network-specific configuration | +| validatorClient.enabled | bool | `true` | Enable the validator client container If you want to use an externally managed validator client. Set this to false, and set your external validator to communicate with the 'validator-api' service created by this chart as if it were a beacon node API. | +| validatorClient.image | object | `{"pullPolicy":"IfNotPresent","repository":"","tag":""}` | Image configuration for validator client Repository and tag will be auto-selected based on validator client type if not specified | +| validatorClient.keystores | object | `{"secretName":""}` | Validator keystores configuration | +| validatorClient.keystores.secretName | string | `""` | Name of the Secret containing validator keystores If provided, skip keystore generation and use existing keys The secret should contain keystore-*.json and keystore-*.txt files Example: kubectl create secret generic validator-keys --from-file=keystore-0.json --from-file=keystore-0.txt secretName: "validator-keys" | +| validatorClient.resources | object | `{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}}` | Resource limits and requests for validator client | +| validatorClient.type | string | `"lighthouse"` | Type of validator client to use Options: lighthouse, teku, prysm, nimbus, lodestar | \ No newline at end of file diff --git a/charts/dv-pod/README.md.gotmpl b/charts/dv-pod/README.md.gotmpl index ca5ef978..ec46003e 100644 --- a/charts/dv-pod/README.md.gotmpl +++ b/charts/dv-pod/README.md.gotmpl @@ -13,8 +13,6 @@ Charon Cluster {{ template "chart.requirementsSection" . }} -{{ template "chart.valuesSection" . }} - # How to use this chart A distributed validator cluster is composed of the following containers: @@ -31,13 +29,13 @@ A distributed validator cluster is composed of the following containers: This chart deploys a single distributed validator pod. You have two options for providing the required artifacts: -### Option 1: Pre-existing DKG artifacts (skip DKG process) +### Option 1: Pre-created keys and artifacts (skips DKG process) -If you already have DKG artifacts from a ceremony, you can provide them to skip the DKG process entirely. +If you already have private keys and charon artifacts from a central key creation or a DKG ceremony, you can provide them to skip the automated DKG process entirely. -#### Example 1a: From a multi-node cluster output (./cluster/ structure) +#### Example 1a: From a `charon create cluster` command output (a `./cluster/nodeN` folder structure) -When you've run DKG for multiple nodes and have a `./cluster/` directory: +When you've created keystores for a full cluster locally and have a `./cluster/` directory: ``` ./cluster/ @@ -57,21 +55,26 @@ When you've run DKG for multiple nodes and have a `./cluster/` directory: ``` To deploy node0 using this chart: -```console -# Create ENR private key secret +```sh +# Put the ENR private key in a secret kubectl create secret generic charon-enr-private-key --from-file=cluster/node0/charon-enr-private-key +# Put the private keys in a kubernetes secret +kubectl create secret generic validator-keys \ + --from-file=cluster/node0/validator_keys/keystore-0.json --from-file=cluster/node0/validator_keys/keystore-0.txt + # Create the cluster lock ConfigMap (same for all nodes) -kubectl create configmap my-cluster-lock --from-file=cluster/node0/cluster-lock.json +kubectl create configmap cluster-lock --from-file=cluster/node0/cluster-lock.json -# Install the chart, referencing your ConfigMap +# Install the chart, referencing your ConfigMap and Secrets helm install my-dv-pod obol/dv-pod \ - --set configMaps.clusterlock=my-cluster-lock + --set configMaps.clusterLock=cluster-lock \ + --set validatorClient.keystores.secretName=validator-keys ``` -Note: The validator keys will be loaded from the persistent volume created during DKG. +That should be enough to get your DV-Pod running, and loaded with the required artifacts, assuming the default beacon node endpoint of the Obol Stack works. -#### Example 1b: From a single node output (.charon/ structure) +#### Example 1b: From a Distributed Key Generation ceremony output (a `.charon/` folder structure) When you have DKG artifacts in a `.charon/` directory: @@ -86,70 +89,28 @@ When you have DKG artifacts in a `.charon/` directory: Create the required resources: -> ⚠️ **IMPORTANT**: The ENR secret MUST be created in the same namespace where you plan to install the Helm chart. The ENR job will only look for secrets in its own namespace. - -```console -# Create namespace (if needed) -kubectl create namespace dv-pod - -# Create ENR private key secret IN THE SAME NAMESPACE +```sh +# Put the charon ENR private key in a kubernetes secret kubectl create secret generic charon-enr-private-key \ - --namespace dv-pod \ --from-file=.charon/charon-enr-private-key -# Create the cluster lock ConfigMap IN THE SAME NAMESPACE -kubectl create configmap my-cluster-lock \ - --namespace dv-pod \ +# Put the private keys in a kubernetes secret +kubectl create secret generic validator-keys \ + --from-file=.charon/validator_keys/keystore-0.json --from-file=.charon/validator_keys/keystore-0.txt + +# Put the cluster lock file in a kubernetes ConfigMap +kubectl create configmap cluster-lock \ --from-file=.charon/cluster-lock.json -# Install the chart IN THE SAME NAMESPACE +# Install the chart helm install my-dv-pod obol/dv-pod \ - --namespace dv-pod \ - --set configMaps.clusterlock=my-cluster-lock -``` - -#### Handling Large Cluster-Lock Files (>1MB) - -If your cluster-lock.json file is larger than 1MB, you may encounter errors when creating the ConfigMap: - -```console -error validating data: ValidationError(ConfigMap.data.cluster-lock.json): invalid type for io.k8s.api.core.v1.ConfigMap.data: got "array", expected "string" + --set configMaps.clusterLock=cluster-lock \ + --set validatorClient.keystores.secretName=validator-keys ``` -In this case, you have two options: - -**Option A: Direct lock hash (Recommended)** - -1. Extract the lock_hash from your cluster-lock.json: - ```console - LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) - echo $LOCK_HASH - ``` +> [!IMPORTANT] +> The ENR secret MUST be created in the same namespace where you plan to install the Helm chart. The ENR job can only look for secrets in its own namespace. -2. Install the chart with the lockHash value: - ```console - helm install my-dv-pod obol/dv-pod \ - --set charon.lockHash=$LOCK_HASH \ - --set charon.operatorAddress= - ``` - -**Option B: ConfigMap approach** - -1. Extract the lock_hash and create a ConfigMap: - ```console - LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) - kubectl create configmap cluster-lock-hash \ - --from-literal=lock-hash=$LOCK_HASH - ``` - -2. Install the chart referencing the ConfigMap: - ```console - helm install my-dv-pod obol/dv-pod \ - --set configMaps.lockHash=cluster-lock-hash \ - --set charon.operatorAddress= - ``` - -The DKG sidecar will use the lock hash to fetch the full cluster lock from the Obol API. ### Option 2: Run DKG through the chart (automatic) @@ -159,7 +120,7 @@ If you don't have pre-existing artifacts, the chart can automatically: 3. Store the resulting artifacts in a persistent volume Simply deploy the chart with your operator address: -```console +```sh helm install my-dv-pod obol/dv-pod \ --set charon.operatorAddress=0xYOUR_OPERATOR_ADDRESS ``` @@ -179,9 +140,7 @@ Install a distributed validator pod: ```sh helm upgrade --install my-dv-pod obol/dv-pod \ --set='charon.beaconNodeEndpoints[0]=' \ - --set='charon.operatorAddress=' \ - --create-namespace \ - --namespace dv-pod + --set='charon.operatorAddress=' ``` ## Validator Client @@ -217,7 +176,7 @@ The chart supports two methods for providing validator keystores: If you have existing keystores, create a Kubernetes secret and reference it: -```console +```sh # Create secret with your keystores kubectl create secret generic validator-keys \ --from-file=keystore-0.json \ @@ -228,7 +187,7 @@ kubectl create secret generic validator-keys \ # Deploy the chart with the keystore secret helm install my-dv-pod obol/dv-pod \ --set validatorClient.keystores.secretName=validator-keys \ - --set configMaps.clusterlock=my-cluster-lock + --set configMaps.clusterLock=my-cluster-lock ``` #### Option 2: DKG-Generated Keystores (Automatic) @@ -285,9 +244,57 @@ For example, if you installed the chart as `my-dv-pod` in namespace `dv-pod`: Note: The Charon validator API on port 3600 provides the same interface as a beacon node API, allowing standard validator clients to connect without modification. +### Handling Large Cluster-Lock Files (>1MB) + +If your `cluster-lock.json` file is larger than 1MB, you may encounter errors when creating the ConfigMap: + +```sh +error validating data: ValidationError(ConfigMap.data.cluster-lock.json): invalid type for io.k8s.api.core.v1.ConfigMap.data: got "array", expected "string" +``` + +In this case, you have two options: + +**Option A: Direct lock hash (Recommended)** + +1. Extract the lock_hash from your cluster-lock.json: + ```sh + LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) + echo $LOCK_HASH + ``` + +2. Install the chart with the lockHash value: + ```sh + helm install my-dv-pod obol/dv-pod \ + --set charon.lockHash=$LOCK_HASH \ + --set charon.operatorAddress= + ``` + +**Option B: ConfigMap approach** + +1. Extract the lock_hash and create a ConfigMap: + ```sh + LOCK_HASH=$(jq -r '.lock_hash' cluster-lock.json) + kubectl create configmap cluster-lock-hash \ + --from-literal=lock-hash=$LOCK_HASH + ``` + +2. Install the chart referencing the ConfigMap: + ```sh + helm install my-dv-pod obol/dv-pod \ + --set configMaps.lockHash=cluster-lock-hash \ + --set charon.operatorAddress= + ``` + +The DKG sidecar will use the lock hash to fetch the full cluster lock from the Obol API. + ## Uninstall the Chart To uninstall and delete the `dv-pod` release: ```sh helm uninstall dv-pod -n dv-pod ``` The command removes all the Kubernetes components associated with the chart and deletes the release. + + +## Full Chart Values Reference + +{{ template "chart.valuesSection" . }} \ No newline at end of file diff --git a/charts/dv-pod/templates/NOTES.txt b/charts/dv-pod/templates/NOTES.txt index 205c1919..847d1178 100644 --- a/charts/dv-pod/templates/NOTES.txt +++ b/charts/dv-pod/templates/NOTES.txt @@ -18,7 +18,7 @@ Your Obol distributed validator pod "{{ template "dv-pod.fullname" . }}" has bee {{- end }} To retrieve the public ENR string: - kubectl get secret -n {{ .Release.Namespace }} {{ include "dv-pod.enrSecretName" . }} -o jsonpath='{.data.public-enr}' | base64 --decode + kubectl get secret -n {{ .Release.Namespace }} {{ include "dv-pod.enrSecretName" . }} -o jsonpath='{.data.enr}' | base64 --decode echo "" {{- if not .Values.configMaps.clusterlock }} diff --git a/charts/dv-pod/templates/_helpers.tpl b/charts/dv-pod/templates/_helpers.tpl index 21b2d016..f3157f85 100644 --- a/charts/dv-pod/templates/_helpers.tpl +++ b/charts/dv-pod/templates/_helpers.tpl @@ -94,19 +94,27 @@ Logic: Return the data key for the ENR private key within the shared secret. */}} {{- define "dv-pod.enrSecretDataKey" -}} -{{- .Values.charon.enr.existingSecret.dataKey | default "private-key" -}} +{{- .Values.charon.enr.existingSecret.privateKeyDataKey | default "charon-enr-private-key" -}} +{{- end -}} + +{{/* +Return the data key for the ENR public key within the shared secret. +*/}} +{{- define "dv-pod.enrSecretPublicDataKey" -}} +{{- .Values.charon.enr.existingSecret.publicKeyDataKey | default "enr" -}} {{- end -}} {{/* Determine if the ENR generation job should be created. -The job should be created only if: -1. Generate is enabled -2. No private key is directly provided -3. No existing secret is explicitly configured -Note: The job will check at runtime if the default secret exists and skip generation if it does +The job should be created if: +1. Generate is enabled AND +2. No private key is directly provided AND +3. ENR job is enabled +Note: The job will run even if an existing secret is specified, as it can handle incomplete secrets +(secrets with only private key missing the public ENR field) */}} {{- define "dv-pod.shouldCreateEnrJob" -}} -{{- if and .Values.charon.enr.generate.enabled (not .Values.charon.enr.privateKey) (not .Values.charon.enr.existingSecret.name) .Values.charon.enrJob.enabled -}} +{{- if and .Values.charon.enr.generate.enabled (not .Values.charon.enr.privateKey) .Values.charon.enrJob.enabled -}} true {{- end -}} {{- end -}} diff --git a/charts/dv-pod/templates/hooks-enr-job.yaml b/charts/dv-pod/templates/hooks-enr-job.yaml index 8ee9226e..61531013 100644 --- a/charts/dv-pod/templates/hooks-enr-job.yaml +++ b/charts/dv-pod/templates/hooks-enr-job.yaml @@ -23,7 +23,7 @@ spec: app.kubernetes.io/component: enr-job-pod spec: restartPolicy: Never - serviceAccountName: {{ include "dv-pod.fullname" . }}-enr-job # Service account with secret permissions + serviceAccountName: {{ include "dv-pod.fullname" . }}-enr-job # Service account with permission to access k8s secrets securityContext: runAsUser: 1000 runAsGroup: 1000 @@ -40,16 +40,53 @@ spec: - -c - | set -e - SECRET_NAME="{{ include "dv-pod.enrSecretName" . }}" + # Get the name of the k8s secret where the private key and public address are saved NAMESPACE="{{ .Release.Namespace }}" - + SECRET_NAME="{{ include "dv-pod.enrSecretName" . }}" + # Get the name of the key the private key value is saved as within the secret + ENR_PRIVATE_KEY_DATA_NAME="{{ include "dv-pod.enrSecretDataKey" . }}" + # Get the name of the key the ENR address value is saved as within the secret + ENR_PUBLIC_ADDRESS_DATA_NAME="{{ include "dv-pod.enrSecretPublicDataKey" . }}" + # Check if the secret already exists echo "Checking if secret '$SECRET_NAME' already exists in namespace '$NAMESPACE'..." if kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then - echo "INFO: Secret '$SECRET_NAME' already exists. Skipping ENR generation." - echo "INFO: The existing secret will be used by the pod." - # Create a marker file to signal other containers to skip - touch /enr-shared/skip-generation + echo "INFO: Secret '$SECRET_NAME' already exists. Checking field completeness..." + + # Check if both required fields exist in the secret + HAS_PRIVATE_KEY="" + HAS_PUBLIC_ENR="" + + if kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" -o jsonpath="{.data.${ENR_PRIVATE_KEY_DATA_NAME}}" >/dev/null 2>&1; then + PRIVATE_KEY_DATA=$(kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" -o jsonpath="{.data.${ENR_PRIVATE_KEY_DATA_NAME}}") + if [ -n "$PRIVATE_KEY_DATA" ]; then + HAS_PRIVATE_KEY="true" + echo "INFO: Private key field '${ENR_PRIVATE_KEY_DATA_NAME}' found in secret." + fi + fi + + if kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" -o jsonpath="{.data.${ENR_PUBLIC_ADDRESS_DATA_NAME}}" >/dev/null 2>&1; then + PUBLIC_ENR_DATA=$(kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" -o jsonpath="{.data.${ENR_PUBLIC_ADDRESS_DATA_NAME}}") + if [ -n "$PUBLIC_ENR_DATA" ]; then + HAS_PUBLIC_ENR="true" + echo "INFO: Public ENR field '${ENR_PUBLIC_ADDRESS_DATA_NAME}' found in secret." + fi + fi + + # If both fields exist, skip generation entirely + if [ "$HAS_PRIVATE_KEY" = "true" ] && [ "$HAS_PUBLIC_ENR" = "true" ]; then + echo "INFO: Both private key and public ENR are present. Skipping ENR generation." + touch /enr-shared/skip-generation + # If only private key exists, generate the missing public ENR + elif [ "$HAS_PRIVATE_KEY" = "true" ] && [ "$HAS_PUBLIC_ENR" != "true" ]; then + echo "INFO: Private key exists but public ENR is missing. Will generate public ENR from existing private key." + # Decode the private key and save it for charon to use + echo "$PRIVATE_KEY_DATA" | base64 -d > /enr-shared/existing-private-key + touch /enr-shared/generate-enr-only + # If neither field exists or only public ENR exists (unusual case), do full generation + else + echo "INFO: Secret exists but required fields are missing or incomplete. Will proceed with full ENR generation." + fi else echo "Secret '$SECRET_NAME' does not exist. ENR generation will proceed." fi @@ -65,17 +102,28 @@ spec: - -c - | set -e - # Check if we should skip generation + # Check if we should skip generation entirely if [ -f /enr-shared/skip-generation ]; then - echo "INFO: Skipping ENR generation as secret already exists." + echo "INFO: Skipping ENR generation as secret already contains both fields." exit 0 fi - + mkdir -p /enr-output - if [ -n "{{ .Values.charon.enr.privateKey }}" ]; then - echo "INFO: Using provided ENR private key." + + # Check if we only need to generate public ENR from existing private key + if [ -f /enr-shared/generate-enr-only ]; then + echo "INFO: Generating public ENR from existing private key in secret." + # Copy the existing private key to the expected location + cp /enr-shared/existing-private-key /enr-output/charon-enr-private-key + chmod 600 /enr-output/charon-enr-private-key + echo "INFO: Deriving public ENR from existing private key using 'charon enr --data-dir /enr-output' ..." + # 'charon enr --data-dir' will read the key from /enr-output/charon-enr-private-key + PUBLIC_ENR_STRING=$(charon enr --data-dir /enr-output) + elif [ -n "{{ .Values.charon.enr.privateKey }}" ]; then + echo "INFO: Using provided ENR private key from values." # Remove 0x prefix and write to the default location within data-dir echo "{{ .Values.charon.enr.privateKey | trimPrefix "0x" }}" > /enr-output/charon-enr-private-key + chmod 600 /enr-output/charon-enr-private-key echo "INFO: Deriving public ENR from provided private key using 'charon enr --data-dir /enr-output' ..." # 'charon enr --data-dir' will read the key from /enr-output/charon-enr-private-key PUBLIC_ENR_STRING=$(charon enr --data-dir /enr-output) @@ -104,7 +152,7 @@ spec: echo "ERROR: Could not extract clean ENR string from output: ${PUBLIC_ENR_STRING}" exit 1 fi - echo "${CLEAN_PUBLIC_ENR_STRING}" > /enr-output/enr_public_string + echo "${CLEAN_PUBLIC_ENR_STRING}" > /enr-output/enr # Ensure the private key file is present and not empty if [ ! -s "/enr-output/charon-enr-private-key" ]; then @@ -128,56 +176,57 @@ spec: set -e SECRET_NAME="{{ include "dv-pod.enrSecretName" . }}" NAMESPACE="{{ .Release.Namespace }}" - + ENR_PUBLIC_ADDRESS_DATA_NAME="{{ include "dv-pod.enrSecretPublicDataKey" . }}" + # Check if we should skip (double-check in case secret was created between containers) if [ -f /enr-shared/skip-generation ]; then - echo "INFO: Skipping secret creation as it already exists." - exit 0 - fi - - # Check again if the secret already exists (race condition protection) - echo "Double-checking if secret '$SECRET_NAME' already exists in namespace '$NAMESPACE'..." - if kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then - echo "INFO: Secret '$SECRET_NAME' already exists. Skipping." + echo "INFO: Skipping secret operations as it already contains both fields." exit 0 fi - - echo "Secret '$SECRET_NAME' does not exist. Creating from generated ENR..." + echo "Waiting for ENR key files in /enr-input..." # Wait for the private key file and the public ENR string file - while [ ! -f /enr-input/charon-enr-private-key ] || [ ! -f /enr-input/enr_public_string ]; do - echo "Waiting for /enr-input/charon-enr-private-key and /enr-input/enr_public_string..." + while [ ! -f /enr-input/charon-enr-private-key ] || [ ! -f /enr-input/enr ]; do + echo "Waiting for /enr-input/charon-enr-private-key and /enr-input/enr..." ls -l /enr-input # For debugging sleep 2 done echo "ENR key files found." PRIVATE_KEY=$(cat /enr-input/charon-enr-private-key) - PUBLIC_ENR=$(cat /enr-input/enr_public_string) - - # Further sanitize PUBLIC_ENR: 'charon create enr' output includes a log line like "Created ENR private key: ..." FIRST, - # and THEN the "enr:-..." string. We only want the "enr:-..." part. - # Grep for the line starting with 'enr:' - PUBLIC_ENR_CLEANED=$(echo "$PUBLIC_ENR" | grep '^enr:') - - if [ -z "$PRIVATE_KEY" ] || [ -z "$PUBLIC_ENR_CLEANED" ]; then - echo "ERROR: Private key or cleaned public ENR is empty." - echo "Original Private Key Content: $(cat /enr-input/charon-enr-private-key)" - echo "Original Public ENR Content from file: $(cat /enr-input/enr_public_string)" - echo "Cleaned Public ENR: $PUBLIC_ENR_CLEANED" + PUBLIC_ENR=$(cat /enr-input/enr) + + if [ -z "$PRIVATE_KEY" ] || [ -z "$PUBLIC_ENR" ]; then + echo "ERROR: Private key or public ENR is empty." + echo "Private Key Content: $(cat /enr-input/charon-enr-private-key | wc -c) characters" + echo "Public ENR Content: $(cat /enr-input/enr)" exit 1 fi - echo "Creating/updating secret '$SECRET_NAME' in namespace '$NAMESPACE'..." - - # Using kubectl create secret generic with --dry-run and kubectl apply for idempotency - kubectl create secret generic "${SECRET_NAME}" \ - --namespace="${NAMESPACE}" \ - --from-literal=private-key="${PRIVATE_KEY}" \ - --from-literal=public-enr="${PUBLIC_ENR_CLEANED}" \ - --dry-run=client -o yaml | kubectl apply -f - + # Check if we're only adding the missing ENR field to an existing secret + if [ -f /enr-shared/generate-enr-only ]; then + echo "INFO: Adding missing public ENR field to existing secret '$SECRET_NAME'..." + # Use kubectl patch to add only the missing field + kubectl patch secret "${SECRET_NAME}" -n "${NAMESPACE}" --type='merge' -p="{\"data\":{\"${ENR_PUBLIC_ADDRESS_DATA_NAME}\":\"$(echo -n "$PUBLIC_ENR" | base64 -w 0)\"}}" + echo "Secret '$SECRET_NAME' updated with missing ENR field successfully." + else + # Check again if the secret already exists (race condition protection) + echo "Double-checking if secret '$SECRET_NAME' already exists in namespace '$NAMESPACE'..." + if kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then + echo "INFO: Secret '$SECRET_NAME' already exists. Skipping creation." + exit 0 + fi + + echo "Creating new secret '$SECRET_NAME' with both fields..." + # Using kubectl create secret generic with --dry-run and kubectl apply for idempotency + kubectl create secret generic "${SECRET_NAME}" \ + --namespace="${NAMESPACE}" \ + --from-literal=charon-enr-private-key="${PRIVATE_KEY}" \ + --from-literal=enr="${PUBLIC_ENR}" \ + --dry-run=client -o yaml | kubectl apply -f - - echo "Secret '$SECRET_NAME' created/updated successfully." + echo "Secret '$SECRET_NAME' created successfully." + fi volumeMounts: - name: enr-data mountPath: /enr-input diff --git a/charts/dv-pod/templates/statefulset.yaml b/charts/dv-pod/templates/statefulset.yaml index e0e7a2ce..b2f08663 100644 --- a/charts/dv-pod/templates/statefulset.yaml +++ b/charts/dv-pod/templates/statefulset.yaml @@ -68,7 +68,7 @@ spec: runAsGroup: 1000 args: - "{{ .Values.charon.operatorAddress }}" - - "/enr-from-job/enr.txt" + - "/enr-from-job/{{ include "dv-pod.enrSecretPublicDataKey" . }}" {{- if .Values.charon.lockHash }} command: - sh @@ -77,7 +77,7 @@ spec: # Write lockHash to file for DKG sidecar echo "{{ .Values.charon.lockHash }}" > /charon-data/lockhash # Execute the original command with the original args - exec node /app/dist/dkg-sidecar.js {{ .Values.charon.operatorAddress }} /enr-from-job/enr.txt + exec node /app/dist/dkg-sidecar.js {{ .Values.charon.operatorAddress }} /enr-from-job/{{ include "dv-pod.enrSecretPublicDataKey" . }} {{- end }} env: - name: POD_NAME @@ -111,26 +111,19 @@ spec: value: "{{ .Values.charon.dkgSidecar.targetConfigHash }}" {{- end }} volumeMounts: - - name: enr-data + - name: charon-enr-private-key mountPath: /enr-from-job readOnly: true - name: charon-data mountPath: /charon-data - {{- if .Values.configMaps.clusterlock }} + {{- if .Values.configMaps.clusterLock }} # Mount pre-existing cluster-lock if provided - name: cluster-lock mountPath: /charon-data/cluster-lock.json subPath: cluster-lock.json readOnly: true {{- end }} - {{- if .Values.configMaps.lockHash }} - # Mount lock-hash for large cluster-lock files - - name: lock-hash - mountPath: /charon-data/lockhash - subPath: lock-hash - readOnly: true - {{- end }} - - name: charon-enr-key + - name: charon-enr-private-key mountPath: /charon-data/charon-enr-private-key subPath: "{{ include "dv-pod.enrSecretDataKey" . }}" resources: @@ -319,10 +312,18 @@ spec: - > exec /usr/local/bin/charon run --beacon-node-endpoints={{ include "dv-pod.beaconNodeEndpoints" . }} + {{- if and .Values.charon.beaconNodeHeaders (not .Values.charon.beaconNodeHeadersSecretName) }} + --beacon-node-headers={{ .Values.charon.beaconNodeHeaders | quote }} + {{- end }} {{- if .Values.charon.fallbackBeaconNodeEndpoints }} --fallback-beacon-node-endpoints={{ include "dv-pod.fallbackBeaconNodeEndpoints" . }} {{- end }} - --lock-file=/charon-data/cluster-lock.json + {{- if .Values.charon.lockFile }} + --lock-file={{ .Values.charon.lockFile }} + {{- end }} + {{- if .Values.charon.nickname }} + --nickname={{ .Values.charon.nickname }} + {{- end }} {{- if .Values.charon.builderApi }} --builder-api={{ .Values.charon.builderApi }} {{- end }} @@ -379,15 +380,9 @@ spec: --p2p-tcp-address={{ .Values.charon.p2pTcpAddress }} {{- end }} --private-key-file="{{ .Values.charon.privateKeyFile }}" - {{- if .Values.charon.syntheticBlockProposals }} - --synthetic-block-proposals={{ .Values.charon.syntheticBlockProposals }} - {{- end }} {{- if .Values.charon.validatorApiAddress }} --validator-api-address={{ .Values.charon.validatorApiAddress }} {{- end }} - {{- if and .Values.charon.beaconNodeHeaders (not .Values.charon.beaconNodeHeadersSecretName) }} - --beacon-node-headers={{ .Values.charon.beaconNodeHeaders | quote }} - {{- end }} env: - name: NODE_NAME valueFrom: @@ -420,7 +415,7 @@ spec: periodSeconds: {{ .Values.livenessProbe.periodSeconds }} httpGet: path: {{ .Values.livenessProbe.httpGet.path }} - port: 3620 + port: {{ .Values.livenessProbe.httpGet.port }} {{- end }} {{- if .Values.readinessProbe.enabled }} readinessProbe: @@ -428,10 +423,10 @@ spec: periodSeconds: {{ .Values.readinessProbe.periodSeconds }} httpGet: path: {{ .Values.readinessProbe.httpGet.path }} - port: 3620 + port: {{ .Values.readinessProbe.httpGet.port }} {{- end }} volumeMounts: - {{- if .Values.configMaps.clusterlock }} + {{- if .Values.configMaps.clusterLock }} # Mount cluster-lock configMap if provided - name: cluster-lock mountPath: /charon-data/cluster-lock.json @@ -439,7 +434,7 @@ spec: readOnly: true {{- end }} # Mount for the shared ENR private key - - name: charon-enr-key + - name: charon-enr-private-key mountPath: "{{ .Values.charon.privateKeyFile }}" subPath: "{{ include "dv-pod.enrSecretDataKey" . }}" # Mount the specific key file, not the whole secret dir - name: charon-data @@ -453,13 +448,20 @@ spec: # Validator Client Container - name: validator-client {{- if eq .Values.validatorClient.type "lighthouse" }} - image: {{ .Values.validatorClient.image.repository | default "sigp/lighthouse" }}:{{ .Values.validatorClient.image.tag | default "v7.0.1" }} + image: {{ .Values.validatorClient.image.repository | default "sigp/lighthouse" }}:{{ .Values.validatorClient.image.tag | default "v7.1.0" }} command: - lighthouse - validator_client - --beacon-nodes=http://localhost:3600 + - --suggested-fee-recipient=0x0000000000000000000000000000000000000000 + {{- if .Values.validatorClient.config.network }} + - --network={{ .Values.validatorClient.config.network }} + {{- end }} - --graffiti={{ .Values.validatorClient.config.graffiti | quote }} - --datadir=/validator-data + {{- if .Values.charon.builderApi }} + - --builder-proposals + {{- end }} - --init-slashing-protection - --metrics - --metrics-address=0.0.0.0 @@ -469,12 +471,15 @@ spec: - {{ . }} {{- end }} {{- else if eq .Values.validatorClient.type "teku" }} - image: {{ .Values.validatorClient.image.repository | default "consensys/teku" }}:{{ .Values.validatorClient.image.tag | default "25.6.0" }} + image: {{ .Values.validatorClient.image.repository | default "consensys/teku" }}:{{ .Values.validatorClient.image.tag | default "25.9.1" }} command: - /opt/teku/bin/teku - validator-client - --network={{ .Values.validatorClient.config.network | default "auto" }} - --beacon-node-api-endpoint=http://localhost:3600 + {{- if .Values.charon.builderApi }} + - --validators-builder-registration-default-enabled=true + {{- end }} - --validators-graffiti={{ .Values.validatorClient.config.graffiti | quote }} - --data-path=/validator-data - --validator-keys=/validator-data/keys:/validator-data/passwords @@ -490,11 +495,18 @@ spec: - {{ . }} {{- end }} {{- else if eq .Values.validatorClient.type "prysm" }} - image: {{ .Values.validatorClient.image.repository | default "gcr.io/prysmaticlabs/prysm/validator" }}:{{ .Values.validatorClient.image.tag | default "v5.1.2" }} + image: {{ .Values.validatorClient.image.repository | default "gcr.io/prysmaticlabs/prysm/validator" }}:{{ .Values.validatorClient.image.tag | default "v6.0.4" }} command: - /app/cmd/validator/validator - --beacon-rest-api-provider=http://localhost:3600 + - --enable-beacon-rest-api + {{- if .Values.validatorClient.config.network }} + - --chain-config-file=/config/{{ .Values.validatorClient.config.network }}.yaml + {{- end }} - --graffiti={{ .Values.validatorClient.config.graffiti | quote }} + {{- if .Values.charon.builderApi }} + - --enable-builder + {{- end }} - --datadir=/validator-data - --monitoring-host=0.0.0.0 - --monitoring-port=5064 @@ -503,10 +515,13 @@ spec: - {{ . }} {{- end }} {{- else if eq .Values.validatorClient.type "nimbus" }} - image: {{ .Values.validatorClient.image.repository | default "statusim/nimbus-eth2" }}:{{ .Values.validatorClient.image.tag | default "multiarch-v25.6.0" }} + image: {{ .Values.validatorClient.image.repository | default "statusim/nimbus-eth2" }}:{{ .Values.validatorClient.image.tag | default "multiarch-v25.9.0" }} command: - /home/user/nimbus-eth2/build/nimbus_validator_client - --beacon-node=http://localhost:3600 + {{- if .Values.charon.builderApi }} + - --payload-builder=true + {{- end }} - --graffiti={{ .Values.validatorClient.config.graffiti | quote }} - --data-dir=/validator-data - --metrics @@ -517,12 +532,19 @@ spec: - {{ . }} {{- end }} {{- else if eq .Values.validatorClient.type "lodestar" }} - image: {{ .Values.validatorClient.image.repository | default "chainsafe/lodestar" }}:{{ .Values.validatorClient.image.tag | default "v1.31.0" }} + image: {{ .Values.validatorClient.image.repository | default "chainsafe/lodestar" }}:{{ .Values.validatorClient.image.tag | default "v1.33.0" }} command: - - node + - node - /usr/app/packages/cli/bin/lodestar - validator - --beacon-nodes=http://localhost:3600 + {{- if .Values.validatorClient.config.network }} + - --network={{ .Values.validatorClient.config.network }} + {{- end }} + {{- if .Values.charon.builderApi }} + - --builder="true" + - --builder.selection="builderonly" + {{- end }} - --graffiti={{ .Values.validatorClient.config.graffiti | quote }} - --dataDir=/validator-data - --importKeystores=/validator-data/keystores @@ -557,39 +579,26 @@ spec: {{- end }} {{- end }} volumes: - {{- if .Values.configMaps.clusterlock }} + {{- if .Values.configMaps.clusterLock }} - name: cluster-lock projected: sources: - configMap: - name: {{ .Values.configMaps.clusterlock }} + name: {{ .Values.configMaps.clusterLock }} optional: true {{- else }} - name: cluster-lock emptyDir: {} {{- end }} - {{- if .Values.configMaps.lockHash }} - # Volume for lock-hash ConfigMap (large cluster-lock files) - - name: lock-hash - configMap: - name: {{ .Values.configMaps.lockHash }} - items: - - key: lock-hash - path: lock-hash - {{- end }} - # Volume for the shared ENR private key - - name: charon-enr-key + # Volume for the shared ENR private key and public ENR + - name: charon-enr-private-key secret: secretName: {{ include "dv-pod.enrSecretName" . }} items: - key: {{ include "dv-pod.enrSecretDataKey" . }} - path: {{ include "dv-pod.enrSecretDataKey" . }} # Ensure the file in the volume has the name of the data key - - name: enr-data - secret: - secretName: {{ include "dv-pod.enrSecretName" . }} - items: - - key: public-enr - path: enr.txt + path: {{ include "dv-pod.enrSecretDataKey" . }} + - key: {{ include "dv-pod.enrSecretPublicDataKey" . }} + path: {{ include "dv-pod.enrSecretPublicDataKey" . }} {{- if .Values.validatorClient.keystores.secretName }} # Volume for validator keystores secret - name: validator-keystores diff --git a/charts/dv-pod/templates/tests/test-default-enr-generation.yaml b/charts/dv-pod/templates/tests/test-default-enr-generation.yaml index f9360e9a..a0c2aa61 100644 --- a/charts/dv-pod/templates/tests/test-default-enr-generation.yaml +++ b/charts/dv-pod/templates/tests/test-default-enr-generation.yaml @@ -44,8 +44,8 @@ spec: echo "ERROR: Secret $SECRET_NAME not found" exit 1 fi - PRIVATE_KEY=$(kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.private-key}' | base64 --decode) - PUBLIC_ENR=$(kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.public-enr}' | base64 --decode) + PRIVATE_KEY=$(kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.charon-enr-private-key}' | base64 --decode) + PUBLIC_ENR=$(kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.enr}' | base64 --decode) if [ -z "$PRIVATE_KEY" ]; then echo "ERROR: Private key not found or empty in secret $SECRET_NAME" diff --git a/charts/dv-pod/templates/tests/test-dkg-sidecar-fresh-state.yaml b/charts/dv-pod/templates/tests/test-dkg-sidecar-fresh-state.yaml index cb1c6540..8ab93422 100644 --- a/charts/dv-pod/templates/tests/test-dkg-sidecar-fresh-state.yaml +++ b/charts/dv-pod/templates/tests/test-dkg-sidecar-fresh-state.yaml @@ -37,7 +37,7 @@ spec: valueFrom: secretKeyRef: name: {{ template "dv-pod.enrSecretName" . }} - key: public-enr + key: enr volumeMounts: - name: enr-from-job mountPath: /enr-from-job diff --git a/charts/dv-pod/templates/tests/test-existing-enr-secret.yaml b/charts/dv-pod/templates/tests/test-existing-enr-secret.yaml index c3e94f18..0853c236 100644 --- a/charts/dv-pod/templates/tests/test-existing-enr-secret.yaml +++ b/charts/dv-pod/templates/tests/test-existing-enr-secret.yaml @@ -59,8 +59,8 @@ spec: fi echo "INFO: Pod $POD_NAME is ready." - PRIVATE_KEY_DATA_KEY="{{ .Values.charon.enr.existingSecret.privateKeyDataKey | default "private-key" }}" - PUBLIC_ENR_DATA_KEY="{{ .Values.charon.enr.existingSecret.publicKeyDataKey | default "public-enr" }}" + PRIVATE_KEY_DATA_KEY="{{ .Values.charon.enr.existingSecret.privateKeyDataKey | default "charon-enr-private-key" }}" + PUBLIC_ENR_DATA_KEY="{{ .Values.charon.enr.existingSecret.publicKeyDataKey | default "enr" }}" echo "INFO: Fetching expected private key (base64) from secret $EXISTING_SECRET_NAME using data key '$PRIVATE_KEY_DATA_KEY'..." EXPECTED_PK_B64=$(kubectl get secret $EXISTING_SECRET_NAME -n $NAMESPACE -o jsonpath="{.data.${PRIVATE_KEY_DATA_KEY}}") diff --git a/charts/dv-pod/templates/tests/test-provided-enr.yaml b/charts/dv-pod/templates/tests/test-provided-enr.yaml index dad2b493..5b873f24 100644 --- a/charts/dv-pod/templates/tests/test-provided-enr.yaml +++ b/charts/dv-pod/templates/tests/test-provided-enr.yaml @@ -48,7 +48,7 @@ spec: SECRET_NAME="{{ template "dv-pod.enrSecretName" . }}" # Fetch the private key from the secret and decode it - ACTUAL_PRIVATE_KEY_B64=$(kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.private-key}') + ACTUAL_PRIVATE_KEY_B64=$(kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.charon-enr-private-key}') if [ -z "$ACTUAL_PRIVATE_KEY_B64" ]; then echo "ERROR: Private key not found in secret $SECRET_NAME" exit 1 @@ -69,10 +69,10 @@ spec: fi echo "INFO: Private key in secret $SECRET_NAME matches provided value." - if ! kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.public-enr}' | grep -v '^$'; then + if ! kubectl get secret $SECRET_NAME -n {{ .Release.Namespace }} -o jsonpath='{.data.enr}' | grep -v '^$'; then echo "ERROR: Public ENR not found or empty in secret $SECRET_NAME (provided key)." exit 1 fi - echo "INFO: Found non-empty public-enr in secret $SECRET_NAME (provided key)." + echo "INFO: Found non-empty enr in secret $SECRET_NAME (provided key)." echo "INFO: Provided ENR test passed." {{- end }} diff --git a/charts/dv-pod/values.yaml b/charts/dv-pod/values.yaml index 84615937..9066cd6a 100644 --- a/charts/dv-pod/values.yaml +++ b/charts/dv-pod/values.yaml @@ -2,17 +2,263 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. +# -- Kubernetes secrets names +# For the ENR, the secret name is either defined in 'charon.enr.existingSecret.name' +# or generated by the ENR job (e.g., {{ .Release.Name }}-dv-pod-enr-key). +secrets: + # -- Default ENR private key secret name to check for auto-detection + # IMPORTANT: This secret MUST exist in the same namespace as the Helm release + # If this secret exists in the release namespace, it will be used automatically + # unless explicitly overridden by charon.enr.existingSecret.name + # The ENR job will NOT check other namespaces to prevent unexpected behavior + defaultEnrPrivateKey: "charon-enr-private-key" + +# -- Kubernetes configMaps names for non-sensitive configuration data. +configMaps: + # -- Name of the ConfigMap containing the cluster-lock.json file + # Set this to the name of an existing ConfigMap to skip the DKG process. + # Example: kubectl create configmap cluster-lock --from-file=.charon/cluster-lock.json + # Then set: clusterLock: "cluster-lock" + # If not set or if the ConfigMap doesn't exist, the DKG process will run. + # NOTE: For large cluster-lock files (>1MB), use charon.lockHash instead + # clusterLock: "cluster-lock" + clusterLock: "" + +# This code block control's charon's configuration, including the DKG initcontainer +charon: + # -- The Ethereum address of this operator. This MUST be provided by the user to use the auto-dkg functionality. + operatorAddress: "" # Set your operator address here. This will be used to sign approvals to DKG invites on the Obol Launchpad. + + # -- The nickname this Charon appears as in monitoring and its peer's logs. Maximum 32 characters. + nickname: "" + + # -- Beacon node endpoints + # The default is the Obol Stack full node address. Change this to your own consensus client beacon API(s). + # All beacon nodes specified here will be called for every charon request, if you want to leverage fallback behaviour + # place secondary beacon nodes in charon.fallbackBeaconNodeEndpoints[] + beaconNodeEndpoints: + - "http://l1-full-node-beacon.l1.cluster.svc.local:5052" + + # -- Fallback beacon node endpoints (optional) + # These will be used if the primary beaconNodeEndpoints are unavailable + fallbackBeaconNodeEndpoints: + # Example: + - "https://ethereum-beacon-api.publicnode.com" + # - "http://backup-beacon-2:5052" + + # -- Beacon node authentication headers + # WARNING: These headers will be sent to ALL beacon nodes, which could leak credentials + # Format: "Authorization=Basic " + # To generate: echo -n "username:password" | base64 + # Example: "Authorization=Basic am9objpkb2U=" + beaconNodeHeaders: "" + + # -- 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 charon.beaconNodeHeadersSecretKey + beaconNodeHeadersSecretName: "" + + # -- Optional: Key within the charon.beaconNodeHeaders secret to read from + beaconNodeHeadersSecretKey: "headers" + + # -- Optional: Execution client RPC endpoint URL (e.g., your Ethereum execution client) + # Note: Charon currently only supports a single execution endpoint + # This is only needed if an operator in the cluster uses a smart contract wallet for an operator signature. + # If that does not apply to this cluster, this field can be left unset. + executionClientRpcEndpoint: "" + + # --- Charon command line options --- + ## ref: https://docs.obol.tech/docs/learn/charon/charon-cli-reference + + # -- Enables the builder api (MEV) for validator proposals. + # The Builder API must also be enabled on your beacon node client(s). + # The Beacon node must either be connected to a mev sidecar or a mev relay to access the builder network. + # More documentation about MEV can be found [here](https://docs.obol.org/advanced-and-troubleshooting/advanced/enable-mev). + builderApi: "true" + + # -- Minimum feature set to enable by default: alpha, beta, or stable. Warning: modify at own risk. (default "stable") + featureSet: "stable" + + # -- Comma-separated list of features to disable, overriding the default minimum feature set. + featureSetDisable: "" + + # -- Comma-separated list of features to enable, overriding the default minimum feature set. + featureSetEnable: "" + + # -- The path on the pod to the cluster lock file that definesthe distributed validator cluster. (default "/charon-data/cluster-lock.json") + lockFile: "/charon-data/cluster-lock.json" + + # -- Cluster lock hash for large cluster-lock files (>1MB) + # When provided, the DKG sidecar will fetch the full cluster-lock from Obol API using this hash + # Extract the hash from a cluster-lock.json using: `jq -r '.lock_hash' cluster-lock.json` + # Alternative to providing the full cluster-lock.json file via the configMaps.clusterLock value + lockHash: "" + + # -- Log format; console, logfmt or json (default "json") + logFormat: "json" + + # -- Log level; debug, info, warn or error (default "info") + logLevel: "info" + + # -- Enables sending of logfmt structured logs to these Loki log aggregation server addresses. This is in addition to normal stderr logs. + lokiAddresses: "" + + # -- Service label sent with logs to Loki. + lokiService: "" + + # -- Listening address (ip and port) for the monitoring API (prometheus, pprof). (default "127.0.0.1:3620") + monitoringAddress: "0.0.0.0:3620" + + # -- Disables cluster definition and lock file verification. + noVerify: false + + # -- Comma-separated list of CIDR subnets for allowing only certain peer connections. Example: 192.168.0.0/16 would permit connections to peers on your local network only. The default is to accept all connections. + p2pAllowlist: "" + + # -- Comma-separated list of CIDR subnets for disallowing certain peer connections. Example: 192.168.0.0/16 would disallow connections to peers on your local network. The default is to accept all connections. + p2pDenylist: "" + + # -- Disables TCP port reuse for outgoing libp2p connections. + p2pDisableReuseport: "" + + # -- The DNS hostname advertised by libp2p. This may be used to advertise an external DNS. + p2pExternalHostname: "" + + # -- The IP address advertised by libp2p. This may be used to advertise an external IP. + p2pExternalIp: "" + + # -- Comma-separated list of libp2p relay URLs or multiaddrs. + # Default list is provided by libp2p and may change over time + p2pRelays: "" + + # -- Comma-separated list of listening TCP addresses (ip and port) for libP2P traffic. Empty default doesn't bind to local port therefore only supports outgoing connections. + p2pTcpAddress: "0.0.0.0:3610" + + # -- Listening address (ip and port) for validator-facing traffic proxying the beacon-node API. (default "127.0.0.1:3600") + validatorApiAddress: "0.0.0.0:3600" + + # -- 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. + directConnectionEnabled: "true" + + # -- Path within the Charon container where the ENR private key file will be mounted. + privateKeyFile: "/data/charon-enr-private-key" + + # -- 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. Change the `confiMaps.clusterLock: cluster-lock in your values.yaml file, + # or pass --set configMaps.clusterLock=cluster-lock in your helm install command. + # 3. The sidecar will detect the mounted 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. + dkgSidecar: + enabled: true + image: + # -- Image repository for the DKG sidecar + repository: "obolnetwork/charon-dkg-sidecar" + tag: "main" + pullPolicy: "IfNotPresent" + # -- Initial delay in seconds before the first retry of a polling cycle. + initialRetryDelaySeconds: 10 + # -- Maximum delay in seconds for exponential backoff between polling cycles. + maxRetryDelaySeconds: 300 + # -- Factor by which the retry delay increases after each polling cycle (e.g., 2 for doubling). + retryDelayFactor: 2 + # -- Delay in seconds between polling retries + retryDelaySeconds: 10 + # -- Page limit for API calls when fetching cluster definitions + pageLimit: 10 + # -- API endpoint for the Obol network to fetch cluster definitions + apiEndpoint: "https://api.obol.tech" + # -- 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" + targetConfigHash: "" + # -- Resources for the cluster poller init container + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + # -- Service account settings for test pods + serviceAccount: + create: true # Whether to specify the test service account for test pods + + enrJob: + # -- 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. + enabled: true + + # --- Configuration for ENR management --- + enr: + # -- Provide the ENR private key directly (hex format, e.g., 0x...). + # If set, 'generate' and 'existingSecret' are ignored. + privateKey: "" + + # -- 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. + existingSecret: + name: "" # Name of the K8s secret (e.g., "my-dv-pod-enr-secret") + # -- Key in the secret's 'data' field holding the private key hex string + privateKeyDataKey: "charon-enr-private-key" + # -- Key in the secret's 'data' field holding the public ENR string + publicKeyDataKey: "enr" + + # -- Enable automatic generation of an ENR private key. + # Only used if 'privateKey' and 'existingSecret.name' are not provided. + # The generated key will be stored in a secret with data keys 'charon-enr-private-key' and 'enr'. + generate: + enabled: true + # -- Annotations to add to the ENR generation job + annotations: {} + # -- Node selector for the ENR generation job + nodeSelector: {} + image: + repository: "obolnetwork/charon" + # Specify a stable tag, e.g., v1.6.0 or check for the latest release [here](https://github.com/ObolNetwork/charon/releases/). + tag: "v1.6.1" + pullPolicy: "IfNotPresent" + # -- Image to use for kubectl operations within the ENR generation job + # This image must contain a compatible kubectl binary. + kubectlImage: + repository: "bitnamisecure/kubectl" + tag: "latest" + pullPolicy: "IfNotPresent" + + + # -- Validator client configuration validatorClient: # -- Enable the validator client container + # If you want to use an externally managed validator client. + # Set this to false, and set your external validator to communicate with the 'validator-api' service created by this chart as if it were a beacon node API. enabled: true # -- Type of validator client to use - # Options: lighthouse, teku, nimbus, lodestar + # Options: lighthouse, teku, prysm, nimbus, lodestar type: "lighthouse" # -- Image configuration for validator client + # Repository and tag will be auto-selected based on validator client type if not specified image: - # Repository will be set based on the type selected repository: "" tag: "" pullPolicy: IfNotPresent @@ -20,8 +266,13 @@ validatorClient: # -- Validator client specific configuration config: # -- Graffiti to include in proposed blocks - graffiti: "DV-Pod" - # -- Additional CLI arguments for the validator client + # Leaving it unset will result in charon defaults. + graffiti: "" + # -- Network configuration for validator client (e.g., mainnet, sepolia) + # Used by some validator clients like Teku for network-specific configuration + network: "" + # -- Additional CLI arguments for the validator client + # (the minimum required mev flags are automatically included when you set .Values.charon.builderApi to true) extraArgs: [] # -- Resource limits and requests for validator client @@ -39,26 +290,39 @@ validatorClient: # If provided, skip keystore generation and use existing keys # The secret should contain keystore-*.json and keystore-*.txt files # Example: kubectl create secret generic validator-keys --from-file=keystore-0.json --from-file=keystore-0.txt + # secretName: "validator-keys" secretName: "" - - # -- Automatically create secret from DKG-generated keystores - # Only applies when secretName is empty and DKG runs successfully - autoCreate: true + +# -- Central Monitoring +centralMonitoring: + # -- Specifies whether central monitoring should be enabled + enabled: false + # -- https endpoint to obol central prometheus + promEndpoint: "https://vm.monitoring.gcp.obol.tech/write" + # -- The authentication token to the central prometheus + token: "" # -- Charon image repository, pull policy, and tag version image: repository: obolnetwork/charon pullPolicy: IfNotPresent - tag: v1.5.1 + tag: v1.6.1 -# -- Chain ID for the network (1: Mainnet, 100: Gnosis, 560048: Hoodi) -chainId: 560048 # Default to Hoodi +# -- Chain ID for the network (1: Mainnet, 11155111: Sepolia, 560048: Hoodi) +# Used for DKG Ceremony +chainId: 1 # Default to Mainnet # -- Credentials to fetch images from private registry ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ imagePullSecrets: [] -# -- Provide a name in place of lighthouse for `app:` labels +# -- Global configuration that can be referenced across the chart +# Used for test configurations and shared settings +global: + # -- Global annotations applied to resources + annotations: {} + +# -- Provide a name in place of charon for `app:` labels nameOverride: "" # -- Provide a name to substitute for the full names of resources @@ -85,8 +349,10 @@ serviceAccount: enabled: true # -- Annotations to add to the service account annotations: {} - # -- The name of the service account to use. If not set and create is true, a name is generated using the default template + # -- The name of the service account to use. If not set, a name is generated using the default template name: "" + # -- The name of the service account to use for test pods. If not set, uses the main service account + nameTest: "" # -- The security context for pods # @default -- See `values.yaml` @@ -198,230 +464,13 @@ podDisruptionBudget: minAvailable: "" enabled: true +# -- Maximum number of pods that can be unavailable for cluster threshold +# Used in pod disruption budget when minAvailable is not specified +clusterThreshold: "" + # -- allows you to configure and disable automated rolling updates for containers, labels, resource request/limits, and annotations for the Pods in a StatefulSet. updateStrategy: RollingUpdate - -charon: - # -- The Ethereum address of this operator. This MUST be provided by the user. - operatorAddress: "" # Required - set your operator address here - - # -- Beacon node endpoints (used when sub-charts are disabled) - # These will be used by both Charon and the validator client - beaconNodeEndpoints: - - "http://beacon-node:5052" - - # -- Execution client RPC endpoint URL (e.g., your Ethereum execution client) - # Note: Charon currently only supports a single execution endpoint - executionClientRpcEndpoint: "" - - # -- Fallback beacon node endpoints (optional) - # These will be used if the primary beaconNodeEndpoints are unavailable - fallbackBeaconNodeEndpoints: [] - # Example: - # - "https://ethereum-beacon-api.publicnode.com" - # - "http://backup-beacon-2:5052" - - # -- Beacon node authentication headers - # WARNING: These headers will be sent to ALL beacon nodes, which could leak credentials - # Format: "Authorization=Basic " - # To generate: echo -n "username:password" | base64 - # Example: "Authorization=Basic am9objpkb2U=" - beaconNodeHeaders: "" - - # -- 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 - beaconNodeHeadersSecretName: "" - - # -- Key within the beacon node headers secret - beaconNodeHeadersSecretKey: "headers" - # -- 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. - dkgSidecar: - enabled: true - image: - # -- Image repository for the DKG sidecar - repository: "ghcr.io/obolnetwork/charon-dkg-sidecar" - tag: "v1.0.0" - pullPolicy: "IfNotPresent" - # -- Initial delay in seconds before the first retry of a polling cycle. - initialRetryDelaySeconds: 10 - # -- Maximum delay in seconds for exponential backoff between polling cycles. - maxRetryDelaySeconds: 300 - # -- Factor by which the retry delay increases after each polling cycle (e.g., 2 for doubling). - retryDelayFactor: 2 - # -- Delay in seconds between polling retries - retryDelaySeconds: 10 - # -- Page limit for API calls when fetching cluster definitions - pageLimit: 10 - # -- API endpoint for the Obol network to fetch cluster definitions - apiEndpoint: "https://api.obol.tech" - # -- 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" - targetConfigHash: "" - # -- Resources for the cluster poller init container - resources: - requests: - cpu: 50m - memory: 128Mi - limits: - cpu: 200m - memory: 256Mi - # -- Service account settings for test pods - serviceAccount: - create: true # Whether to specify the test service account for test pods - - enrJob: - # -- 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. - enabled: true - - # --- Configuration for ENR management --- - enr: - # -- Provide the ENR private key directly (hex format, e.g., 0x...). - # If set, 'generate' and 'existingSecret' are ignored. - privateKey: "" - - # -- 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. - existingSecret: - name: "" # Name of the K8s secret (e.g., "my-dv-pod-enr-secret") - dataKey: "private-key" # Key in the secret's 'data' field holding the private key hex string. - - # -- 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). - generate: - enabled: true - image: - repository: "obolnetwork/charon" - # Specify a stable tag, e.g., v1.4.2 or check for latest on Docker Hub - tag: "v1.5.1" - pullPolicy: "IfNotPresent" - # -- Image to use for kubectl operations within the ENR generation job - # This image must contain a compatible kubectl binary and shell. - kubectlImage: - repository: "alpine/k8s" - tag: "1.31.4" - pullPolicy: "IfNotPresent" - - # --- Charon command line options --- - ## ref: https://docs.obol.tech/docs/charon/charon_cli_reference - - # -- 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. - builderApi: "" - - # -- Minimum feature set to enable by default: alpha, beta, or stable. Warning: modify at own risk. (default "stable") - featureSet: "stable" - - # -- Comma-separated list of features to disable, overriding the default minimum feature set. - featureSetDisable: "" - - # -- Comma-separated list of features to enable, overriding the default minimum feature set. - featureSetEnable: "" - - # -- The path to the cluster lock file defining distributed validator cluster. (default ".charon/cluster-lock.json") - lockFile: "/charon-data/cluster-lock.json" - - # -- Log format; console, logfmt or json (default "console") - logFormat: "json" - - # -- Log level; debug, info, warn or error (default "info") - logLevel: "info" - - # -- Enables sending of logfmt structured logs to these Loki log aggregation server addresses. This is in addition to normal stderr logs. - lokiAddresses: "" - - # -- Service label sent with logs to Loki. - lokiService: "" - - # -- Listening address (ip and port) for the monitoring API (prometheus, pprof). (default "127.0.0.1:3620") - monitoringAddress: "0.0.0.0:3620" - - # -- Disables cluster definition and lock file verification. - noVerify: false - - # -- Comma-separated list of CIDR subnets for allowing only certain peer connections. Example: 192.168.0.0/16 would permit connections to peers on your local network only. The default is to accept all connections. - p2pAllowlist: "" - - # -- Comma-separated list of CIDR subnets for disallowing certain peer connections. Example: 192.168.0.0/16 would disallow connections to peers on your local network. The default is to accept all connections. - p2pDenylist: "" - - # -- Disables TCP port reuse for outgoing libp2p connections. - p2pDisableReuseport: "" - - # -- The DNS hostname advertised by libp2p. This may be used to advertise an external DNS. - p2pExternalHostname: "" - - # -- The IP address advertised by libp2p. This may be used to advertise an external IP. - p2pExternalIp: "" - - # -- Comma-separated list of libp2p relay URLs or multiaddrs. - # Default list is provided by libp2p and may change over time - p2pRelays: "" - - # -- Comma-separated list of listening TCP addresses (ip and port) for libP2P traffic. Empty default doesn't bind to local port therefore only supports outgoing connections. - p2pTcpAddress: "0.0.0.0:3610" - # -- Listening address (ip and port) for validator-facing traffic proxying the beacon-node API. (default "127.0.0.1:3600") - validatorApiAddress: "0.0.0.0:3600" - - # -- 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. - directConnectionEnabled: "true" - - # -- Path within the Charon container where the ENR private key file will be mounted. - privateKeyFile: "/data/charon-enr-private-key" - - -# -- Kubernetes secrets names that might be used as suffixes or for other purposes. -# For the ENR, the secret name is either defined in 'charon.enr.existingSecret.name' -# or generated by the ENR job (e.g., {{ .Release.Name }}-dv-pod-enr-key). -secrets: - # -- Suffix for ENR private key secret (used internally by templates) - enrPrivateKey: "charon-enr-private-key" - # -- Default ENR private key secret name to check for auto-detection - # IMPORTANT: This secret MUST exist in the same namespace as the Helm release - # If this secret exists in the release namespace, it will be used automatically - # unless explicitly overridden by charon.enr.existingSecret.name - # The ENR job will NOT check other namespaces to prevent unexpected behavior - defaultEnrPrivateKey: "charon-enr-private-key" - -# -- Kubernetes configMaps names for non-sensitive configuration data. -configMaps: - # -- Name of the ConfigMap containing the cluster-lock.json file - # Set this to the name of an existing ConfigMap to skip the DKG process. - # Example: If you have created a ConfigMap named "my-cluster-lock": - # kubectl create configmap my-cluster-lock --from-file=cluster-lock.json - # Then set: clusterlock: "my-cluster-lock" - # If not set or if the ConfigMap doesn't exist, the DKG process will run. - # Note: ConfigMaps support larger file sizes than Secrets (up to 1MB compressed), making them - # more suitable for cluster-lock files which can be several megabytes. - clusterlock: "" - - # -- Persistence configuration for DKG artifacts and charon data persistence: # -- Enable persistence using a PersistentVolumeClaim. @@ -438,9 +487,9 @@ persistence: # NOTE: Validator data ALWAYS uses persistent storage to prevent slashing, # even if persistence.enabled is false for charon-data. validatorDataSize: 500Mi - # -- Optional: Annotations for the PVC - # annotations: {} - # key: value + # -- Annotations for the PVC + # Example: annotations: {"volume.beta.kubernetes.io/storage-class": "fast"} + annotations: {} # -- Prometheus Service Monitor @@ -476,6 +525,8 @@ livenessProbe: periodSeconds: 5 httpGet: path: /livez + # -- Port for liveness probe HTTP checks + port: 3620 # -- Configure readiness probes ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ @@ -485,15 +536,8 @@ readinessProbe: periodSeconds: 3 httpGet: path: /readyz - -# -- Central Monitoring -centralMonitoring: - # -- Specifies whether central monitoring should be enabled - enabled: false - # -- https endpoint to obol central prometheus - promEndpoint: "https://vm.monitoring.gcp.obol.tech/write" - # -- The authentication token to the central prometheus - token: "" + # -- Port for readiness probe HTTP checks + port: 3620 # -- Configuration for running Helm tests. # These values are typically only used when `helm test` is run. diff --git a/charts/obol-app/templates/_helpers.tpl b/charts/obol-app/templates/_helpers.tpl index 62f91044..928cc843 100644 --- a/charts/obol-app/templates/_helpers.tpl +++ b/charts/obol-app/templates/_helpers.tpl @@ -65,9 +65,15 @@ Create the name of the service account to use Enhanced image helper with registry support */}} {{- define "obol-app.image" -}} +{{- $tag := .Values.image.tag }} +{{- if and $tag (ne $tag "") }} +{{- $tag = $tag }} +{{- else }} +{{- $tag = "latest" }} +{{- end }} {{- if .Values.image.registry }} -{{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository (.Values.image.tag | default "latest") }} +{{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository $tag }} {{- else }} -{{- printf "%s:%s" .Values.image.repository (.Values.image.tag | default "latest") }} +{{- printf "%s:%s" .Values.image.repository $tag }} {{- end }} {{- end }}