From c7c15cdcd56f59ede8491ba866fca92956835300 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 14:35:12 +0200 Subject: [PATCH 01/17] chore: update configuration files for network-bootstrapper chart - Updated biome.jsonc to include linter rules for suspicious code. - Adjusted package.json to ensure consistent command execution. - Enhanced README.md with additional options for CLI commands. - Modified .vscode/settings.json to enforce formatting on save and paste. - Updated Helm chart files for network-bootstrapper, including values and templates for deployment. - Refined test cases in build-command.test.ts and output.test.ts to align with new configurations. --- .vscode/settings.json | 1 + README.md | 31 +- biome.jsonc | 3 + .../.helmignore | 0 charts/network/Chart.lock | 6 + charts/network/Chart.yaml | 15 + charts/network/README.md | 19 ++ .../charts/network-bootstrapper/.helmignore | 23 ++ .../charts}/network-bootstrapper/Chart.yaml | 0 .../charts}/network-bootstrapper/README.md | 3 +- .../network-bootstrapper/templates/NOTES.txt | 0 .../templates/_helpers.tpl | 0 .../network-bootstrapper/templates/job.yaml | 4 +- .../network-bootstrapper/templates/role.yaml | 0 .../templates/rolebinding.yaml | 0 .../templates/serviceaccount.yaml | 0 .../charts}/network-bootstrapper/values.yaml | 2 - .../network/charts/network-nodes/.helmignore | 23 ++ .../network/charts/network-nodes/Chart.yaml | 10 + .../charts/network-nodes/templates/NOTES.txt | 35 ++ .../network-nodes/templates/_helpers.tpl | 62 ++++ .../network-nodes/templates/configmap.yaml | 66 ++++ .../network-nodes/templates/httproute.yaml | 38 +++ .../network-nodes/templates/ingress.yaml | 43 +++ .../charts/network-nodes/templates/route.yaml | 44 +++ .../network-nodes/templates/service-p2p.yaml | 17 + .../network-nodes/templates/service.yaml | 33 ++ .../templates/serviceaccount.yaml | 13 + .../templates/statefulset-rpc.yaml | 220 +++++++++++++ .../templates/statefulset-validator.yaml | 220 +++++++++++++ .../templates/tests/test-connection.yaml | 15 + .../network/charts/network-nodes/values.yaml | 304 ++++++++++++++++++ charts/network/templates/NOTES.txt | 0 charts/network/templates/_helpers.tpl | 62 ++++ charts/network/values.yaml | 0 package.json | 6 +- src/cli/build-command.test.ts | 62 ++-- src/cli/build-command.ts | 66 ++-- src/cli/output.test.ts | 11 +- src/cli/output.ts | 25 +- 40 files changed, 1357 insertions(+), 125 deletions(-) rename charts/{network-bootstrapper => network}/.helmignore (100%) create mode 100644 charts/network/Chart.lock create mode 100644 charts/network/Chart.yaml create mode 100644 charts/network/README.md create mode 100644 charts/network/charts/network-bootstrapper/.helmignore rename charts/{ => network/charts}/network-bootstrapper/Chart.yaml (100%) rename charts/{ => network/charts}/network-bootstrapper/README.md (94%) rename charts/{ => network/charts}/network-bootstrapper/templates/NOTES.txt (100%) rename charts/{ => network/charts}/network-bootstrapper/templates/_helpers.tpl (100%) rename charts/{ => network/charts}/network-bootstrapper/templates/job.yaml (96%) rename charts/{ => network/charts}/network-bootstrapper/templates/role.yaml (100%) rename charts/{ => network/charts}/network-bootstrapper/templates/rolebinding.yaml (100%) rename charts/{ => network/charts}/network-bootstrapper/templates/serviceaccount.yaml (100%) rename charts/{ => network/charts}/network-bootstrapper/values.yaml (97%) create mode 100644 charts/network/charts/network-nodes/.helmignore create mode 100644 charts/network/charts/network-nodes/Chart.yaml create mode 100644 charts/network/charts/network-nodes/templates/NOTES.txt create mode 100644 charts/network/charts/network-nodes/templates/_helpers.tpl create mode 100644 charts/network/charts/network-nodes/templates/configmap.yaml create mode 100644 charts/network/charts/network-nodes/templates/httproute.yaml create mode 100644 charts/network/charts/network-nodes/templates/ingress.yaml create mode 100644 charts/network/charts/network-nodes/templates/route.yaml create mode 100644 charts/network/charts/network-nodes/templates/service-p2p.yaml create mode 100644 charts/network/charts/network-nodes/templates/service.yaml create mode 100644 charts/network/charts/network-nodes/templates/serviceaccount.yaml create mode 100644 charts/network/charts/network-nodes/templates/statefulset-rpc.yaml create mode 100644 charts/network/charts/network-nodes/templates/statefulset-validator.yaml create mode 100644 charts/network/charts/network-nodes/templates/tests/test-connection.yaml create mode 100644 charts/network/charts/network-nodes/values.yaml create mode 100644 charts/network/templates/NOTES.txt create mode 100644 charts/network/templates/_helpers.tpl create mode 100644 charts/network/values.yaml diff --git a/.vscode/settings.json b/.vscode/settings.json index 5fe7817..33d286c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "editor.defaultFormatter": "biomejs.biome" }, "typescript.tsdk": "node_modules/typescript/lib", + "prettier.enable": false, "editor.formatOnSave": true, "editor.formatOnPaste": true, "emmet.showExpandedAbbreviation": "never", diff --git a/README.md b/README.md index ced1781..6fe3e69 100644 --- a/README.md +++ b/README.md @@ -9,30 +9,15 @@ The helm chart to run this on Kubernetes / OpenShift can be found [here](./chart ## CLI usage ``` -Usage: network-bootstrapper [options] +Usage: network-bootstrapper [options] [command] -Generate node identities, configure consensus, and emit a Besu genesis. +Utilities for configuring Besu-based networks. Options: - -v, --validators Number of validator nodes to generate. - (default: 4) - -r, --rpc-nodes Number of RPC nodes to generate. (default: 2) - -a, --allocations Path to a genesis allocations JSON file. - (default: none) - -o, --outputType Output target (screen, file, kubernetes). - (default: "screen") - --consensus Consensus algorithm (IBFTv2, QBFT). (default: - QBFT) - --chain-id Chain ID for the genesis config. (default: - random between 40000 and 50000) - --seconds-per-block Block time in seconds. (default: 2) - --gas-limit Block gas limit in decimal form. (default: - 9007199254740991) - --gas-price Base gas price (wei). (default: 0) - --evm-stack-size EVM stack size limit. (default: 2048) - --contract-size-limit Contract size limit in bytes. (default: - 2147483647) - --accept-defaults Accept default values for all prompts when CLI - flags are omitted. (default: disabled) - -h, --help display help for command + -h, --help display help for command + +Commands: + generate [options] Generate node identities, configure consensus, and emit a + Besu genesis. + help [command] display help for command ``` diff --git a/biome.jsonc b/biome.jsonc index 1e2f179..80c3f80 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,6 +1,9 @@ { "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "extends": ["ultracite"], + "files": { + "includes": ["**", "!**/*.yaml"] + }, "linter": { "rules": { "suspicious": { diff --git a/charts/network-bootstrapper/.helmignore b/charts/network/.helmignore similarity index 100% rename from charts/network-bootstrapper/.helmignore rename to charts/network/.helmignore diff --git a/charts/network/Chart.lock b/charts/network/Chart.lock new file mode 100644 index 0000000..5e3ead6 --- /dev/null +++ b/charts/network/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: network-bootstrapper + repository: "" + version: '*' +digest: sha256:dbed3aed691528082678797a03e8695a7e3857d059d09793ec6b1019277e836d +generated: "2025-09-17T13:10:16.197366+02:00" diff --git a/charts/network/Chart.yaml b/charts/network/Chart.yaml new file mode 100644 index 0000000..838d134 --- /dev/null +++ b/charts/network/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: network +description: A Helm chart for a blockchain network on Kubernetes +type: application +version: 0.1.0 +appVersion: "0.1.0" +maintainers: + - name: SettleMint + email: support@settlemint.com + url: https://settlemint.com +dependencies: + - name: network-bootstrapper + version: "*" + - name: network-nodes + version: "*" diff --git a/charts/network/README.md b/charts/network/README.md new file mode 100644 index 0000000..d9f1063 --- /dev/null +++ b/charts/network/README.md @@ -0,0 +1,19 @@ +# network + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) + +A Helm chart for a blockchain network on Kubernetes + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| SettleMint | | | + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| | network-bootstrapper | * | +| | network-nodes | * | + diff --git a/charts/network/charts/network-bootstrapper/.helmignore b/charts/network/charts/network-bootstrapper/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/network/charts/network-bootstrapper/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/network-bootstrapper/Chart.yaml b/charts/network/charts/network-bootstrapper/Chart.yaml similarity index 100% rename from charts/network-bootstrapper/Chart.yaml rename to charts/network/charts/network-bootstrapper/Chart.yaml diff --git a/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md similarity index 94% rename from charts/network-bootstrapper/README.md rename to charts/network/charts/network-bootstrapper/README.md index 64a48d1..0dbb239 100644 --- a/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -35,7 +35,7 @@ A Helm chart for Kubernetes | serviceAccount.automount | bool | `true` | Automatically mount the service account token into the pod. | | serviceAccount.create | bool | `true` | Whether to create a service account automatically. | | serviceAccount.name | string | `""` | Existing service account name to use instead of one generated by the chart. If unset and `serviceAccount.create` is true, a name is derived from the chart fullname. | -| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","rpcNodes":null,"secondsPerBlock":null,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | +| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","secondsPerBlock":null,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | | settings.allocations | string | `nil` | Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. | | settings.chainId | int | `nil` | Explicit chain ID applied to the genesis configuration. Defaults to a random value in the 40000-50000 range when omitted. | | settings.consensus | string | `nil` | Consensus engine to configure for the network (IBFTv2 or QBFT). Default: "QBFT". | @@ -44,7 +44,6 @@ A Helm chart for Kubernetes | settings.gasLimit | int | `nil` | Genesis block gas limit value expressed in decimal. Default: 9007199254740991. | | settings.gasPrice | int | `nil` | Base gas price in wei applied to the chain. Default: 0. | | settings.outputType | string | `"kubernetes"` | Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". | -| settings.rpcNodes | int | `nil` | Number of RPC node definitions included in the output topology. Default: 2. | | settings.secondsPerBlock | int | `nil` | Target block time in seconds encoded into genesis. Default: 2. | | settings.validators | int | `nil` | Number of validator node definitions the bootstrapper generates. Default: 4. | | tolerations | list | `[]` | Kubernetes tolerations assigned to the bootstrapper pod. | diff --git a/charts/network-bootstrapper/templates/NOTES.txt b/charts/network/charts/network-bootstrapper/templates/NOTES.txt similarity index 100% rename from charts/network-bootstrapper/templates/NOTES.txt rename to charts/network/charts/network-bootstrapper/templates/NOTES.txt diff --git a/charts/network-bootstrapper/templates/_helpers.tpl b/charts/network/charts/network-bootstrapper/templates/_helpers.tpl similarity index 100% rename from charts/network-bootstrapper/templates/_helpers.tpl rename to charts/network/charts/network-bootstrapper/templates/_helpers.tpl diff --git a/charts/network-bootstrapper/templates/job.yaml b/charts/network/charts/network-bootstrapper/templates/job.yaml similarity index 96% rename from charts/network-bootstrapper/templates/job.yaml rename to charts/network/charts/network-bootstrapper/templates/job.yaml index 19486f0..4171008 100644 --- a/charts/network-bootstrapper/templates/job.yaml +++ b/charts/network/charts/network-bootstrapper/templates/job.yaml @@ -38,12 +38,10 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: + - generate {{- with .Values.settings.validators }} - --validators={{ . }} {{- end }} - {{- with .Values.settings.rpcNodes }} - - --rpc-nodes={{ . }} - {{- end }} {{- with .Values.settings.allocations }} - --allocations={{ . }} {{- end }} diff --git a/charts/network-bootstrapper/templates/role.yaml b/charts/network/charts/network-bootstrapper/templates/role.yaml similarity index 100% rename from charts/network-bootstrapper/templates/role.yaml rename to charts/network/charts/network-bootstrapper/templates/role.yaml diff --git a/charts/network-bootstrapper/templates/rolebinding.yaml b/charts/network/charts/network-bootstrapper/templates/rolebinding.yaml similarity index 100% rename from charts/network-bootstrapper/templates/rolebinding.yaml rename to charts/network/charts/network-bootstrapper/templates/rolebinding.yaml diff --git a/charts/network-bootstrapper/templates/serviceaccount.yaml b/charts/network/charts/network-bootstrapper/templates/serviceaccount.yaml similarity index 100% rename from charts/network-bootstrapper/templates/serviceaccount.yaml rename to charts/network/charts/network-bootstrapper/templates/serviceaccount.yaml diff --git a/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml similarity index 97% rename from charts/network-bootstrapper/values.yaml rename to charts/network/charts/network-bootstrapper/values.yaml index 3d01ab0..cef294d 100644 --- a/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -91,8 +91,6 @@ affinity: {} settings: # -- (int) Number of validator node definitions the bootstrapper generates. Default: 4. validators: - # -- (int) Number of RPC node definitions included in the output topology. Default: 2. - rpcNodes: # -- (string) Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. allocations: # -- (string) Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". diff --git a/charts/network/charts/network-nodes/.helmignore b/charts/network/charts/network-nodes/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/network/charts/network-nodes/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/network/charts/network-nodes/Chart.yaml b/charts/network/charts/network-nodes/Chart.yaml new file mode 100644 index 0000000..b34678a --- /dev/null +++ b/charts/network/charts/network-nodes/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: network-nodes +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "0.1.0" +maintainers: + - name: SettleMint + email: support@settlemint.com + url: https://settlemint.com diff --git a/charts/network/charts/network-nodes/templates/NOTES.txt b/charts/network/charts/network-nodes/templates/NOTES.txt new file mode 100644 index 0000000..6f0a610 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/NOTES.txt @@ -0,0 +1,35 @@ +1. Get the application URL by running these commands: +{{- if .Values.httpRoute.enabled }} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} + echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nodes.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nodes.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nodes.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nodes.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/charts/network/charts/network-nodes/templates/_helpers.tpl b/charts/network/charts/network-nodes/templates/_helpers.tpl new file mode 100644 index 0000000..1113259 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "nodes.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nodes.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nodes.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nodes.labels" -}} +helm.sh/chart: {{ include "nodes.chart" . }} +{{ include "nodes.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nodes.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nodes.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "nodes.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "nodes.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/network/charts/network-nodes/templates/configmap.yaml b/charts/network/charts/network-nodes/templates/configmap.yaml new file mode 100644 index 0000000..6f6aba8 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/configmap.yaml @@ -0,0 +1,66 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nodes.fullname" . }}-config + labels: + {{- include "nodes.labels" . | nindent 4 }} +{{- $persistence := .Values.persistence | default (dict) }} +{{- $persistenceEnabled := default false (get $persistence "enabled") }} +{{- $mountPath := default "" (get $persistence "mountPath") }} +{{- $dataPath := default "/data" .Values.config.dataPath }} +{{- if and $persistenceEnabled (ne $mountPath "") }} +{{- $dataPath = $mountPath }} +{{- end }} +data: + config.toml: | + genesis-file = "/etc/besu/genesis.json" + logging = "{{ .Values.config.logging }}" + node-private-key-file = "/etc/besu/privatekey" + data-storage-format = "{{ .Values.config.dataStorageFormat }}" + bonsai-limit-trie-logs-enabled = {{ .Values.config.bonsaiLimitTrieLogsEnabled }} + data-path = "{{ $dataPath }}" + cache-last-blocks = {{ .Values.config.cacheLastBlocks }} + min-gas-price = {{ .Values.config.minGasPrice }} + tx-pool = "{{ .Values.config.txPool.type }}" + tx-pool-max-size = {{ .Values.config.txPool.maxSize }} + tx-pool-no-local-priority = {{ .Values.config.txPool.noLocalPriority }} + tx-pool-limit-by-account-percentage = {{ .Values.config.txPool.limitByAccountPercentage }} + tx-pool-enable-save-restore = {{ .Values.config.txPool.enableSaveRestore }} + p2p-enabled = {{ .Values.config.p2p.enabled }} + discovery-enabled = {{ .Values.config.p2p.discoveryEnabled }} + static-nodes-file = "{{ .Values.config.p2p.staticNodesFile }}" + p2p-interface = "{{ .Values.config.p2p.interface }}" + p2p-port = {{ .Values.service.ports.rlpx }} + max-peers = {{ .Values.config.p2p.maxPeers }} + rpc-tx-feecap = {{ .Values.config.rpc.txFeecap }} + sync-min-peers = {{ .Values.config.sync.minPeers }} + sync-mode = "{{ .Values.config.sync.mode }}" + remote-connections-limit-enabled = {{ .Values.config.remoteConnectionsLimitEnabled }} + random-peer-priority-enabled = {{ .Values.config.randomPeerPriorityEnabled }} + receipt-compaction-enabled = {{ .Values.config.receiptCompactionEnabled }} + host-allowlist = {{ mustToJson .Values.config.hostAllowlist }} + rpc-http-enabled = {{ .Values.config.http.enabled }} + rpc-http-host = "{{ .Values.config.http.host }}" + rpc-http-port = {{ .Values.service.ports.rpc }} + rpc-http-api = {{ mustToJson .Values.config.http.api }} + rpc-http-cors-origins = {{ mustToJson .Values.config.http.corsOrigins }} + rpc-http-authentication-enabled = {{ .Values.config.http.authenticationEnabled }} + revert-reason-enabled = {{ .Values.config.revertReasonEnabled }} + rpc-http-max-active-connections = {{ .Values.config.http.maxActiveConnections }} + rpc-ws-max-active-connections = {{ .Values.config.ws.maxActiveConnections }} + rpc-http-max-request-content-length = {{ .Values.config.http.maxRequestContentLength }} + rpc-ws-max-frame-size = {{ .Values.config.ws.maxFrameSize }} + rpc-http-max-batch-size = {{ .Values.config.http.maxBatchSize }} + graphql-http-enabled = {{ .Values.config.graphql.enabled }} + graphql-http-host = "{{ .Values.config.graphql.host }}" + graphql-http-port = {{ .Values.service.ports.graphql }} + graphql-http-cors-origins = {{ mustToJson .Values.config.graphql.corsOrigins }} + rpc-ws-enabled = {{ .Values.config.ws.enabled }} + rpc-ws-host = "{{ .Values.config.ws.host }}" + rpc-ws-port = {{ .Values.service.ports.ws }} + rpc-ws-api = {{ mustToJson .Values.config.ws.api }} + rpc-ws-authentication-enabled = {{ .Values.config.ws.authenticationEnabled }} + metrics-enabled = {{ .Values.config.metrics.enabled }} + metrics-host = "{{ .Values.config.metrics.host }}" + metrics-port = {{ .Values.service.ports.metrics }} + metrics-category = {{ mustToJson .Values.config.metrics.categories }} diff --git a/charts/network/charts/network-nodes/templates/httproute.yaml b/charts/network/charts/network-nodes/templates/httproute.yaml new file mode 100644 index 0000000..5957f1d --- /dev/null +++ b/charts/network/charts/network-nodes/templates/httproute.yaml @@ -0,0 +1,38 @@ +{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include "nodes.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include "nodes.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} diff --git a/charts/network/charts/network-nodes/templates/ingress.yaml b/charts/network/charts/network-nodes/templates/ingress.yaml new file mode 100644 index 0000000..56f3f44 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "nodes.fullname" . }} + labels: + {{- include "nodes.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "nodes.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/network/charts/network-nodes/templates/route.yaml b/charts/network/charts/network-nodes/templates/route.yaml new file mode 100644 index 0000000..969c71d --- /dev/null +++ b/charts/network/charts/network-nodes/templates/route.yaml @@ -0,0 +1,44 @@ +{{- /* OpenShift Route for exposing the node service when enabled. */ -}} +{{- if .Values.openShiftRoute.enabled -}} +{{- $fullName := include "nodes.fullname" . -}} +{{- $routeValues := .Values.openShiftRoute -}} +{{- $targetPort := default "http" (dig "port" "targetPort" $routeValues) -}} +{{- $weight := default 100 (dig "to" "weight" $routeValues) -}} +{{- $wildcard := default "None" $routeValues.wildcardPolicy -}} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ $fullName }} + labels: + {{- include "nodes.labels" . | nindent 4 }} + {{- with $routeValues.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + to: + kind: Service + name: {{ $fullName }} + {{- if ne $weight 100 }} + weight: {{ $weight }} + {{- end }} + {{- with $routeValues.alternateBackends }} + alternateBackends: + {{- toYaml . | nindent 4 }} + {{- end }} + port: + targetPort: {{ $targetPort }} + {{- with $routeValues.host }} + host: {{ . | quote }} + {{- end }} + {{- with $routeValues.path }} + path: {{ . | quote }} + {{- end }} + {{- if $wildcard }} + wildcardPolicy: {{ $wildcard }} + {{- end }} + {{- with $routeValues.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/network/charts/network-nodes/templates/service-p2p.yaml b/charts/network/charts/network-nodes/templates/service-p2p.yaml new file mode 100644 index 0000000..fc66483 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/service-p2p.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nodes.fullname" . }}-p2p + labels: {{- include "nodes.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.p2pType }} + ports: + - name: rlpx + port: {{ .Values.service.ports.rlpx }} + targetPort: rlpx + protocol: TCP + - name: discovery + port: {{ .Values.service.ports.discovery }} + targetPort: discovery + protocol: UDP + selector: {{- include "nodes.selectorLabels" . | nindent 4 }} diff --git a/charts/network/charts/network-nodes/templates/service.yaml b/charts/network/charts/network-nodes/templates/service.yaml new file mode 100644 index 0000000..dc8f1f0 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/service.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nodes.fullname" . }} + labels: {{- include "nodes.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - name: json-rpc + port: {{ .Values.service.ports.rpc }} + targetPort: json-rpc + protocol: TCP + - name: ws + port: {{ .Values.service.ports.ws }} + targetPort: ws + protocol: TCP + - name: graphql + port: {{ .Values.service.ports.graphql }} + targetPort: graphql + protocol: TCP + - name: rlpx + port: {{ .Values.service.ports.rlpx }} + targetPort: rlpx + protocol: TCP + - name: discovery + port: {{ .Values.service.ports.discovery }} + targetPort: discovery + protocol: UDP + - name: metrics + port: {{ .Values.service.ports.metrics }} + targetPort: metrics + protocol: TCP + selector: {{- include "nodes.selectorLabels" . | nindent 4 }} diff --git a/charts/network/charts/network-nodes/templates/serviceaccount.yaml b/charts/network/charts/network-nodes/templates/serviceaccount.yaml new file mode 100644 index 0000000..46bb324 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nodes.serviceAccountName" . }} + labels: + {{- include "nodes.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml new file mode 100644 index 0000000..f8313fc --- /dev/null +++ b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml @@ -0,0 +1,220 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "nodes.fullname" . }}-rpc + labels: + {{- include "nodes.labels" . | nindent 4 }} +spec: + {{- $root := . }} + {{- $persistence := .Values.persistence | default (dict) }} + {{- $persistenceEnabled := default false (get $persistence "enabled") }} + {{- $existingClaim := default "" (get $persistence "existingClaim") }} + {{- $volumeName := default "data" (get $persistence "volumeName") }} + {{- $mountPath := default "" (get $persistence "mountPath") }} + {{- $subPath := get $persistence "subPath" }} + {{- $readOnly := default false (get $persistence "readOnly") }} + {{- $storageClass := get $persistence "storageClass" }} + {{- $size := default "" (get $persistence "size") }} + {{- $accessModes := default (list "ReadWriteOnce") (get $persistence "accessModes") }} + {{- $volumeMode := get $persistence "volumeMode" }} + {{- $claimAnnotations := default (dict) (get $persistence "annotations") }} + {{- $claimSelector := default (dict) (get $persistence "selector") }} + {{- $retention := default (dict) (get $persistence "retention") }} + {{- $whenDeleted := default "" (get $retention "whenDeleted") }} + {{- $whenScaled := default "" (get $retention "whenScaled") }} + {{- $useExistingClaim := and $persistenceEnabled (ne $existingClaim "") }} + {{- $useClaimTemplate := and $persistenceEnabled (not $useExistingClaim) }} + {{- $shouldMountPersistence := and $persistenceEnabled (ne $mountPath "") }} + {{- $rpcReplicaBudget := .Values.rpcReplicaCount | int }} + replicas: {{ .Values.rpcReplicaCount }} + serviceName: {{ include "nodes.fullname" . }} + {{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }} + persistentVolumeClaimRetentionPolicy: + {{- if ne $whenDeleted "" }} + whenDeleted: {{ $whenDeleted }} + {{- end }} + {{- if ne $whenScaled "" }} + whenScaled: {{ $whenScaled }} + {{- end }} + {{- end }} + selector: + matchLabels: + {{- include "nodes.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nodes.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nodes.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/sh + - -c + args: + - | + exec + /opt/besu/bin/besu \ + --config-file=/etc/besu/config.toml \ + --Xdns-enabled=true \ + --Xdns-update-enabled=true \ + --min-gas-price={{ .Values.config.minGasPrice }} + ports: + - name: json-rpc + port: {{ .Values.service.ports.rpc }} + targetPort: json-rpc + protocol: TCP + - name: ws + port: {{ .Values.service.ports.ws }} + targetPort: ws + protocol: TCP + - name: graphql + port: {{ .Values.service.ports.graphql }} + targetPort: graphql + protocol: TCP + - name: rlpx + port: {{ .Values.service.ports.rlpx }} + targetPort: rlpx + protocol: TCP + - name: discovery + port: {{ .Values.service.ports.discovery }} + targetPort: discovery + protocol: UDP + - name: metrics + port: {{ .Values.service.ports.metrics }} + targetPort: metrics + protocol: TCP + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- $extraMounts := .Values.volumeMounts | default (list) }} + volumeMounts: + - name: besu-config + mountPath: /etc/besu/config.toml + subPath: config.toml + readOnly: true + - name: besu-genesis + mountPath: /etc/besu/genesis.json + subPath: genesis.json + readOnly: true + - name: besu-private-key + mountPath: /etc/besu/privatekey + subPathExpr: "$(POD_NAME)/privateKey" + readOnly: true + {{- if $shouldMountPersistence }} + - name: {{ $volumeName }} + mountPath: {{ $mountPath }} + {{- if $subPath }} + subPath: {{ $subPath }} + {{- end }} + {{- if $readOnly }} + readOnly: true + {{- end }} + {{- end }} + {{- with $extraMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- $extraVolumes := .Values.volumes | default (list) }} + volumes: + - name: besu-config + configMap: + name: {{ include "nodes.fullname" . }}-config + - name: besu-genesis + configMap: + name: besu-genesis + - name: besu-private-key + projected: + {{- if gt $rpcReplicaBudget 0 }} + sources: + {{- range $i, $_ := until $rpcReplicaBudget }} + - secret: + name: {{ printf "besu-node-rpc-node-%d-private-key" (add $i 1) }} + items: + - key: privateKey + path: {{ printf "%s-rpc-%d/privateKey" (include "nodes.fullname" $root) $i }} + {{- end }} + {{- else }} + sources: [] + {{- end }} + {{- if $useExistingClaim }} + - name: {{ $volumeName }} + persistentVolumeClaim: + claimName: {{ $existingClaim }} + {{- end }} + {{- with $extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if $useClaimTemplate }} + volumeClaimTemplates: + - metadata: + name: {{ $volumeName }} + {{- if gt (len $claimAnnotations) 0 }} + annotations: + {{- toYaml $claimAnnotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range $accessModes }} + - {{ . }} + {{- end }} + {{- if $storageClass }} + storageClassName: {{ $storageClass | quote }} + {{- end }} + {{- if $volumeMode }} + volumeMode: {{ $volumeMode }} + {{- end }} + resources: + requests: + storage: {{ required "persistence.size must be set when persistence.enabled is true and persistence.existingClaim is not defined" $size }} + {{- if gt (len $claimSelector) 0 }} + selector: + {{- toYaml $claimSelector | nindent 10 }} + {{- end }} + {{- end }} diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml new file mode 100644 index 0000000..95d0a99 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -0,0 +1,220 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "nodes.fullname" . }}-validator + labels: + {{- include "nodes.labels" . | nindent 4 }} +spec: + {{- $root := . }} + {{- $persistence := .Values.persistence | default (dict) }} + {{- $persistenceEnabled := default false (get $persistence "enabled") }} + {{- $existingClaim := default "" (get $persistence "existingClaim") }} + {{- $volumeName := default "data" (get $persistence "volumeName") }} + {{- $mountPath := default "" (get $persistence "mountPath") }} + {{- $subPath := get $persistence "subPath" }} + {{- $readOnly := default false (get $persistence "readOnly") }} + {{- $storageClass := get $persistence "storageClass" }} + {{- $size := default "" (get $persistence "size") }} + {{- $accessModes := default (list "ReadWriteOnce") (get $persistence "accessModes") }} + {{- $volumeMode := get $persistence "volumeMode" }} + {{- $claimAnnotations := default (dict) (get $persistence "annotations") }} + {{- $claimSelector := default (dict) (get $persistence "selector") }} + {{- $retention := default (dict) (get $persistence "retention") }} + {{- $whenDeleted := default "" (get $retention "whenDeleted") }} + {{- $whenScaled := default "" (get $retention "whenScaled") }} + {{- $useExistingClaim := and $persistenceEnabled (ne $existingClaim "") }} + {{- $useClaimTemplate := and $persistenceEnabled (not $useExistingClaim) }} + {{- $shouldMountPersistence := and $persistenceEnabled (ne $mountPath "") }} + {{- $validatorReplicaBudget := .Values.validatorReplicaCount | int }} + replicas: {{ .Values.validatorReplicaCount }} + serviceName: {{ include "nodes.fullname" . }} + {{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }} + persistentVolumeClaimRetentionPolicy: + {{- if ne $whenDeleted "" }} + whenDeleted: {{ $whenDeleted }} + {{- end }} + {{- if ne $whenScaled "" }} + whenScaled: {{ $whenScaled }} + {{- end }} + {{- end }} + selector: + matchLabels: + {{- include "nodes.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nodes.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nodes.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/sh + - -c + args: + - | + exec + /opt/besu/bin/besu \ + --config-file=/etc/besu/config.toml \ + --Xdns-enabled=true \ + --Xdns-update-enabled=true \ + --min-gas-price={{ .Values.config.minGasPrice }} + ports: + - name: json-rpc + port: {{ .Values.service.ports.rpc }} + targetPort: json-rpc + protocol: TCP + - name: ws + port: {{ .Values.service.ports.ws }} + targetPort: ws + protocol: TCP + - name: graphql + port: {{ .Values.service.ports.graphql }} + targetPort: graphql + protocol: TCP + - name: rlpx + port: {{ .Values.service.ports.rlpx }} + targetPort: rlpx + protocol: TCP + - name: discovery + port: {{ .Values.service.ports.discovery }} + targetPort: discovery + protocol: UDP + - name: metrics + port: {{ .Values.service.ports.metrics }} + targetPort: metrics + protocol: TCP + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- $extraMounts := .Values.volumeMounts | default (list) }} + volumeMounts: + - name: besu-config + mountPath: /etc/besu/config.toml + subPath: config.toml + readOnly: true + - name: besu-genesis + mountPath: /etc/besu/genesis.json + subPath: genesis.json + readOnly: true + - name: besu-private-key + mountPath: /etc/besu/privatekey + subPathExpr: "$(POD_NAME)/privateKey" + readOnly: true + {{- if $shouldMountPersistence }} + - name: {{ $volumeName }} + mountPath: {{ $mountPath }} + {{- if $subPath }} + subPath: {{ $subPath }} + {{- end }} + {{- if $readOnly }} + readOnly: true + {{- end }} + {{- end }} + {{- with $extraMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- $extraVolumes := .Values.volumes | default (list) }} + volumes: + - name: besu-config + configMap: + name: {{ include "nodes.fullname" . }}-config + - name: besu-genesis + configMap: + name: besu-genesis + - name: besu-private-key + projected: + {{- if gt $validatorReplicaBudget 0 }} + sources: + {{- range $i, $_ := until $validatorReplicaBudget }} + - secret: + name: {{ printf "besu-node-validator-%d-private-key" (add $i 1) }} + items: + - key: privateKey + path: {{ printf "%s-validator-%d/privateKey" (include "nodes.fullname" $root) $i }} + {{- end }} + {{- else }} + sources: [] + {{- end }} + {{- if $useExistingClaim }} + - name: {{ $volumeName }} + persistentVolumeClaim: + claimName: {{ $existingClaim }} + {{- end }} + {{- with $extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if $useClaimTemplate }} + volumeClaimTemplates: + - metadata: + name: {{ $volumeName }} + {{- if gt (len $claimAnnotations) 0 }} + annotations: + {{- toYaml $claimAnnotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range $accessModes }} + - {{ . }} + {{- end }} + {{- if $storageClass }} + storageClassName: {{ $storageClass | quote }} + {{- end }} + {{- if $volumeMode }} + volumeMode: {{ $volumeMode }} + {{- end }} + resources: + requests: + storage: {{ required "persistence.size must be set when persistence.enabled is true and persistence.existingClaim is not defined" $size }} + {{- if gt (len $claimSelector) 0 }} + selector: + {{- toYaml $claimSelector | nindent 10 }} + {{- end }} + {{- end }} diff --git a/charts/network/charts/network-nodes/templates/tests/test-connection.yaml b/charts/network/charts/network-nodes/templates/tests/test-connection.yaml new file mode 100644 index 0000000..f7e50e7 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "nodes.fullname" . }}-test-connection" + labels: + {{- include "nodes.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "nodes.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml new file mode 100644 index 0000000..812333b --- /dev/null +++ b/charts/network/charts/network-nodes/values.yaml @@ -0,0 +1,304 @@ +# Default values for nodes. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This sets the replica count for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +rpcReplicaCount: 2 +# RPC pods expect Secrets named `besu-node-rpc-node--private-key` (1-indexed) to exist for private key mounts. +# Validator pods expect Secrets named `besu-node-validator--private-key` (1-indexed). +validatorReplicaCount: 4 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: docker.io/hyperledger/besu + # This sets the pull policy for images. + pullPolicy: IfNotPresent + tag: "25.8.0" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: 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 fullname template + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + p2pType: NodePort + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + ports: + rpc: 8545 + ws: 8546 + graphql: 8547 + rlpx: 30303 + discovery: 30303 + metrics: 9545 + +config: + logging: INFO + dataStorageFormat: FOREST + dataPath: /var/lib/besu + bonsaiLimitTrieLogsEnabled: false + cacheLastBlocks: 1024 + minGasPrice: 0 + txPool: + type: SEQUENCED + maxSize: 100000 + noLocalPriority: true + limitByAccountPercentage: 1 + enableSaveRestore: true + p2p: + enabled: true + discoveryEnabled: true + staticNodesFile: "/etc/besu/static-nodes.json" + interface: "0.0.0.0" + maxPeers: 25 + rpc: + txFeecap: 0 + sync: + minPeers: 1 + mode: FULL + remoteConnectionsLimitEnabled: false + randomPeerPriorityEnabled: true + receiptCompactionEnabled: true + hostAllowlist: + - "*" + http: + enabled: true + host: "0.0.0.0" + api: + - DEBUG + - ETH + - ADMIN + - WEB3 + - IBFT + - NET + - TRACE + - QBFT + - PERM + - TXPOOL + - PLUGINS + corsOrigins: + - all + authenticationEnabled: false + maxActiveConnections: 2000 + maxRequestContentLength: 524288000 + maxBatchSize: 512 + ws: + enabled: true + host: "0.0.0.0" + api: + - DEBUG + - ETH + - ADMIN + - WEB3 + - IBFT + - NET + - TRACE + - QBFT + - PERM + - TXPOOL + - PLUGINS + authenticationEnabled: false + maxActiveConnections: 2000 + maxFrameSize: 2097152 + graphql: + enabled: true + host: "0.0.0.0" + corsOrigins: + - all + metrics: + enabled: true + host: "0.0.0.0" + categories: + - BLOCKCHAIN + - ETHEREUM + - EXECUTORS + - JVM + - NETWORK + - PEERS + - PROCESS + - PRUNER + - RPC + - SYNCHRONIZER + - TRANSACTION_POOL + revertReasonEnabled: true + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- Expose the service via gateway-api HTTPRoute +# Requires Gateway API resources and suitable controller installed within the cluster +# (see: https://gateway-api.sigs.k8s.io/guides/) +httpRoute: + # HTTPRoute enabled. + enabled: false + # HTTPRoute annotations. + annotations: {} + # Which Gateways this Route is attached to. + parentRefs: + - name: gateway + sectionName: http + # namespace: default + # Hostnames matching HTTP header. + hostnames: + - chart-example.local + # List of rules and filters applied. + rules: + - matches: + - path: + type: PathPrefix + value: /headers + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + +# -- Expose the service via OpenShift Route when running on OpenShift clusters +# This relies on the OpenShift router to make the network nodes reachable externally. +openShiftRoute: + enabled: false + # Route annotations transmitted to the OpenShift router. + annotations: {} + # Custom host name for the route. Leave empty to let the cluster allocate one. + host: "" + # Optional path to scope traffic. + path: "" + # Wildcard behaviour; defaults to "None" when left empty. + wildcardPolicy: "" + port: + # Target service port exposed through the route. + targetPort: http + to: + # Adjust when balancing against multiple services; default weight 100 is omitted from the manifest. + weight: 100 + # Extra service backends following the RouteTargetReference schema. + alternateBackends: [] + # TLS termination settings; set to null to disable, or provide termination, certificates, etc. + tls: null + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +# Persistent storage configuration for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +persistence: + enabled: false + # When enabled without an existingClaim, a PersistentVolumeClaim is created per replica. + mountPath: /var/lib/besu + volumeName: data + accessModes: + - ReadWriteOnce + # size is required when persistence.enabled is true and existingClaim is not set. Increase storage by resizing the bound PVC or switching to an existingClaim. + size: 20Gi + storageClass: "" + volumeMode: "" + # Set existingClaim to reuse a pre-provisioned PVC that you manage (supports manual resizing). + existingClaim: "" + # Optional PVC metadata. + annotations: {} + selector: {} + retention: + whenDeleted: Retain + whenScaled: Delete + # Optional mount fine-tuning. + subPath: "" + readOnly: false + + +# Additional volumes on the output StatefulSet definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output StatefulSet definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/charts/network/templates/NOTES.txt b/charts/network/templates/NOTES.txt new file mode 100644 index 0000000..e69de29 diff --git a/charts/network/templates/_helpers.tpl b/charts/network/templates/_helpers.tpl new file mode 100644 index 0000000..c8c5040 --- /dev/null +++ b/charts/network/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "network.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "network.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "network.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "network.labels" -}} +helm.sh/chart: {{ include "network.chart" . }} +{{ include "network.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "network.selectorLabels" -}} +app.kubernetes.io/name: {{ include "network.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "network.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "network.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/network/values.yaml b/charts/network/values.yaml new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 3fa1a42..35c62b7 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,11 @@ "check": "ultracite check", "check:fix": "ultracite fix", "test": "bun test", - "helm": "helm upgrade --install networkbootstrapper ./charts/network-bootstrapper -n networkbootstrapper --create-namespace --timeout 15m", + "helm": "helm upgrade --install network ./charts/network -n network --create-namespace --timeout 15m", "docs:helm": "helm-docs --chart-search-root=. --skip-version-footer", "docs:cli": "cat README.tpl > README.md && echo '\n```' >> README.md && bun src/index.ts --help >> README.md && echo '```' >> README.md", - "package:pack": "helm package charts/network-bootstrapper --destination .", - "package:push:harbor": "helm push ./network-bootstrapper-*.tgz oci://harbor.settlemint.com/atk" + "package:pack": "helm package charts/network --destination .", + "package:push:harbor": "helm push ./network-*.tgz oci://harbor.settlemint.com/atk" }, "devDependencies": { "@biomejs/biome": "2.2.4", diff --git a/src/cli/build-command.test.ts b/src/cli/build-command.test.ts index 8e05a8a..0a30086 100644 --- a/src/cli/build-command.test.ts +++ b/src/cli/build-command.test.ts @@ -10,12 +10,9 @@ import type { OutputPayload, OutputType } from "./output.ts"; import { outputResult as realOutputResult } from "./output.ts"; const VALIDATOR_LABEL = "validator nodes"; -const RPC_LABEL = "RPC nodes"; const VALIDATOR_RETURN = 2; -const RPC_RETURN = 1; const GENESIS_MARKER = '"extraData": "0xextra"'; const EXPECTED_DEFAULT_VALIDATOR = 4; -const EXPECTED_DEFAULT_RPC = 2; const HEX_RADIX = 16; const PAD_WIDTH = 2; const PAD_CHAR = "0"; @@ -24,8 +21,7 @@ const PRIVATE_KEY_REPEAT = 32; const PUBLIC_KEY_REPEAT = 64; const FIRST_VALIDATOR_INDEX = 1; const SECOND_VALIDATOR_INDEX = 2; -const FAUCET_INDEX = 4; -const CLI_FAUCET_INDEX = 3; +const FAUCET_INDEX = VALIDATOR_RETURN + 1; const createFactoryStub = () => { let counter = 0; return { @@ -92,10 +88,7 @@ describe("CLI command bootstrap", () => { factory, promptForCount: (label, provided, defaultValue) => { promptCalls.push([label, provided, defaultValue]); - const value = label.startsWith("validator") - ? VALIDATOR_RETURN - : RPC_RETURN; - return Promise.resolve(value); + return Promise.resolve(VALIDATOR_RETURN); }, promptForGenesis: ( _service, @@ -155,12 +148,10 @@ describe("CLI command bootstrap", () => { expect(promptCalls).toEqual([ [VALIDATOR_LABEL, undefined, EXPECTED_DEFAULT_VALIDATOR], - [RPC_LABEL, undefined, EXPECTED_DEFAULT_RPC], ]); const output = stdout.read(); expect(output).toContain("Genesis"); expect(output).toContain("Validator Nodes"); - expect(output).toContain("RPC Nodes"); expect(output).toContain(GENESIS_MARKER); expect(loadAllocationsPath).toBe("/tmp/alloc.json"); expect(outputInvocation?.type).toBe("screen"); @@ -194,7 +185,7 @@ describe("CLI command bootstrap", () => { algorithm: preset?.algorithm ?? ALGORITHM.QBFT, config: { chainId: preset?.chainId ?? 1, - faucetWalletAddress: expectedAddress(CLI_FAUCET_INDEX), + faucetWalletAddress: expectedAddress(VALIDATOR_RETURN + 1), gasLimit: "0x1", secondsPerBlock: preset?.secondsPerBlock ?? 1, gasPrice: preset?.gasPrice ?? 0, @@ -215,10 +206,9 @@ describe("CLI command bootstrap", () => { [ "node", "cli", + "generate", "--validators", "2", - "--rpc-nodes", - "1", "--allocations", "/tmp/mock.json", "--consensus", @@ -246,6 +236,8 @@ describe("CLI command bootstrap", () => { test("runBootstrap bypasses genesis prompts when CLI overrides provided", async () => { const factory = createFactoryStub(); + const validatorOverride = 1; + const deps: BootstrapDependencies = { factory, promptForCount: (_label, provided) => { @@ -271,7 +263,7 @@ describe("CLI command bootstrap", () => { algorithm: ALGORITHM.IBFTv2, config: { chainId: 1234, - faucetWalletAddress: expectedAddress(CLI_FAUCET_INDEX), + faucetWalletAddress: expectedAddress(validatorOverride + 1), gasLimit: "0x1", secondsPerBlock: 6, }, @@ -287,8 +279,7 @@ describe("CLI command bootstrap", () => { }; const options: CliOptions = { - validators: 1, - rpcNodes: 1, + validators: validatorOverride, consensus: ALGORITHM.IBFTv2, chainId: 1234, secondsPerBlock: 6, @@ -305,8 +296,11 @@ describe("CLI command bootstrap", () => { const shouldReject = async (args: string[], message: string) => { const command = createCliCommand(); command.exitOverride(); + for (const child of command.commands) { + child.exitOverride(); + } await expect( - command.parseAsync(["node", "cli", ...args]) + command.parseAsync(["node", "cli", "generate", ...args]) ).rejects.toThrow(message); }; @@ -331,8 +325,11 @@ describe("CLI command bootstrap", () => { test("createCliCommand rejects unsupported output type", async () => { const command = createCliCommand(); command.exitOverride(); + for (const child of command.commands) { + child.exitOverride(); + } await expect( - command.parseAsync(["node", "cli", "--outputType", "invalid"]) + command.parseAsync(["node", "cli", "generate", "--outputType", "invalid"]) ).rejects.toThrow( `Output type must be one of: ${["screen", "file", "kubernetes"].join(", ")}.` ); @@ -347,7 +344,7 @@ describe("CLI command bootstrap", () => { algorithm: ALGORITHM.QBFT, config: { chainId: 1, - faucetWalletAddress: expectedAddress(CLI_FAUCET_INDEX), + faucetWalletAddress: expectedAddress(EXPECTED_DEFAULT_VALIDATOR + 1), gasLimit: "0x1", gasPrice: 0, secondsPerBlock: 2, @@ -364,22 +361,38 @@ describe("CLI command bootstrap", () => { }; const command = createCliCommand(deps); + const generate = command.commands.find( + (child) => child.name() === "generate" + ); + expect(generate).toBeDefined(); command.exitOverride(); + for (const child of command.commands) { + child.exitOverride(); + } await expect( command.parseAsync( - ["node", "cli", '--outputType="kubernetes"', "--accept-defaults"], + [ + "node", + "cli", + "generate", + '--outputType="kubernetes"', + "--accept-defaults", + ], { from: "node" } ) ).resolves.toBeDefined(); - expect(command.opts().outputType).toBe("kubernetes"); + expect(generate?.opts().outputType).toBe("kubernetes"); expect(capturedOutputType).toBe("kubernetes"); }); test("createCliCommand rejects unsupported consensus", async () => { const command = createCliCommand(); command.exitOverride(); + for (const child of command.commands) { + child.exitOverride(); + } await expect( - command.parseAsync(["node", "cli", "--consensus", "invalid"]) + command.parseAsync(["node", "cli", "generate", "--consensus", "invalid"]) ).rejects.toThrow( `Consensus must be one of: ${Object.values(ALGORITHM).join(", ")}.` ); @@ -413,7 +426,7 @@ describe("CLI command bootstrap", () => { config: { chainId: 1, faucetWalletAddress: expectedAddress( - EXPECTED_DEFAULT_VALIDATOR + EXPECTED_DEFAULT_RPC + 1 + EXPECTED_DEFAULT_VALIDATOR + 1 ), gasLimit: "0x1", secondsPerBlock: 2, @@ -428,7 +441,6 @@ describe("CLI command bootstrap", () => { }, outputResult: (_type, payload) => { expect(payload.validators).toHaveLength(EXPECTED_DEFAULT_VALIDATOR); - expect(payload.rpcNodes).toHaveLength(EXPECTED_DEFAULT_RPC); return Promise.resolve(); }, }; diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 20e8b40..2250c23 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -25,7 +25,6 @@ type CliOptions = { evmStackSize?: number; gasLimit?: string; gasPrice?: number; - rpcNodes?: number; validators?: number; outputType?: OutputType; secondsPerBlock?: number; @@ -41,7 +40,6 @@ type BootstrapDependencies = { }; const DEFAULT_VALIDATOR_COUNT = 4; -const DEFAULT_RPC_COUNT = 2; const OUTPUT_CHOICES: OutputType[] = ["screen", "file", "kubernetes"]; // Normalizes CLI inputs wrapped by orchestrators that keep literal quotes. @@ -110,7 +108,6 @@ const runBootstrap = async ( gasLimit, gasPrice, outputType, - rpcNodes: rpcNodeOption, secondsPerBlock, validators: validatorOption, } = options; @@ -134,14 +131,8 @@ const runBootstrap = async ( validatorOption, DEFAULT_VALIDATOR_COUNT ); - const rpcNodeCount = await resolveCount( - "RPC nodes", - rpcNodeOption, - DEFAULT_RPC_COUNT - ); const validators = generateGroup(deps.factory, validatorsCount); - const rpcNodes = generateGroup(deps.factory, rpcNodeCount); const faucet = deps.factory.generate(); const validatorAddresses = validators.map((node) => node.address); @@ -171,7 +162,6 @@ const runBootstrap = async ( const payload: OutputPayload = { faucet, genesis, - rpcNodes, validators, }; @@ -196,21 +186,22 @@ const createCliCommand = ( command .name("network-bootstrapper") + .description("Utilities for configuring Besu-based networks."); + + // Keep the root command free of options so future subcommands can compose alongside generate. + const generate = command + .command("generate") .description( "Generate node identities, configure consensus, and emit a Besu genesis." - ) + ); + + generate .option( "-v, --validators ", "Number of validator nodes to generate.", createCountParser("Validators"), DEFAULT_VALIDATOR_COUNT ) - .option( - "-r, --rpc-nodes ", - "Number of RPC nodes to generate.", - createCountParser("RPC nodes"), - DEFAULT_RPC_COUNT - ) .option( "-a, --allocations ", "Path to a genesis allocations JSON file. (default: none)" @@ -282,31 +273,26 @@ const createCliCommand = ( .option( "--accept-defaults", "Accept default values for all prompts when CLI flags are omitted. (default: disabled)" - ); - - command.action(async (options: CliOptions, cmd: Command) => { - const normalizedOptions: CliOptions = { - ...options, - validators: - cmd.getOptionValueSource("validators") === "default" - ? undefined - : options.validators, - rpcNodes: - cmd.getOptionValueSource("rpcNodes") === "default" - ? undefined - : options.rpcNodes, - }; + ) + .action(async (options: CliOptions, cmd: Command) => { + const normalizedOptions: CliOptions = { + ...options, + validators: + cmd.getOptionValueSource("validators") === "default" + ? undefined + : options.validators, + }; - const sanitizedOptions: CliOptions = { - ...normalizedOptions, - allocations: - normalizedOptions.allocations === undefined - ? undefined - : stripSurroundingQuotes(normalizedOptions.allocations), - }; + const sanitizedOptions: CliOptions = { + ...normalizedOptions, + allocations: + normalizedOptions.allocations === undefined + ? undefined + : stripSurroundingQuotes(normalizedOptions.allocations), + }; - await runBootstrap(sanitizedOptions, deps); - }); + await runBootstrap(sanitizedOptions, deps); + }); return command; }; diff --git a/src/cli/output.test.ts b/src/cli/output.test.ts index a3e5f59..7312400 100644 --- a/src/cli/output.test.ts +++ b/src/cli/output.test.ts @@ -75,10 +75,9 @@ const PRIVATE_KEY_HEX_LENGTH = 64; const PUBLIC_KEY_HEX_LENGTH = 128; const HEX_RADIX = 16; const SAMPLE_VALIDATOR_INDEX = 1; -const SAMPLE_RPC_INDEX = 2; const SAMPLE_FAUCET_INDEX = 99; -const EXPECTED_CONFIGMAP_COUNT = 9; -const EXPECTED_SECRET_COUNT = 3; +const EXPECTED_CONFIGMAP_COUNT = 6; +const EXPECTED_SECRET_COUNT = 2; const HEX_PREFIX_PATTERN = /^0x/; const TEST_CHAIN_ID = 1; const HTTP_CONFLICT_STATUS = 409; @@ -103,7 +102,6 @@ const sampleNode = (index: number): IndexedNode => { const samplePayload: OutputPayload = { faucet: sampleNode(SAMPLE_FAUCET_INDEX), genesis: { config: { chainId: TEST_CHAIN_ID }, extraData: "0xabc" }, - rpcNodes: [sampleNode(SAMPLE_RPC_INDEX)], validators: [sampleNode(SAMPLE_VALIDATOR_INDEX)], }; @@ -134,10 +132,6 @@ describe("outputResult", () => { "besu-faucet-enode", "besu-faucet-private-key", "besu-faucet-pubkey", - "besu-node-rpc-node-2-address", - "besu-node-rpc-node-2-enode", - "besu-node-rpc-node-2-private-key", - "besu-node-rpc-node-2-pubkey", "besu-node-validator-1-address", "besu-node-validator-1-enode", "besu-node-validator-1-private-key", @@ -241,7 +235,6 @@ describe("outputResult", () => { const secretNames = createdSecrets.map((entry) => entry.name).sort(); expect(secretNames).toEqual([ "besu-faucet-private-key", - "besu-node-rpc-node-2-private-key", "besu-node-validator-1-private-key", ]); const privateKeySecret = createdSecrets.find((entry) => diff --git a/src/cli/output.ts b/src/cli/output.ts index c3056ee..01d2e87 100644 --- a/src/cli/output.ts +++ b/src/cli/output.ts @@ -13,7 +13,6 @@ type OutputType = "screen" | "file" | "kubernetes"; type OutputPayload = { faucet: GeneratedNodeKey; genesis: unknown; - rpcNodes: readonly IndexedNode[]; validators: readonly IndexedNode[]; }; @@ -65,7 +64,6 @@ const outputToScreen = (payload: OutputPayload): void => { process.stdout.write("\n\n"); printGenesis("Genesis", genesisJson); printGroup("Validator Nodes", payload.validators); - printGroup("RPC Nodes", payload.rpcNodes); printFaucet(payload.faucet); }; @@ -87,8 +85,7 @@ const outputToFile = async (payload: OutputPayload): Promise => { const directory = join(OUTPUT_DIR, timestamp); await mkdir(directory, { recursive: true }); - const validatorSpecs = createSpecsForGroup("validator", payload.validators); - const rpcSpecs = createSpecsForGroup("rpc-node", payload.rpcNodes); + const validatorSpecs = createValidatorSpecs(payload.validators); const faucetSpecs: ConfigMapSpec[] = [ { @@ -118,7 +115,7 @@ const outputToFile = async (payload: OutputPayload): Promise => { join(directory, "genesis"), `${JSON.stringify(payload.genesis, null, 2)}\n` ), - ...[...validatorSpecs, ...rpcSpecs, ...faucetSpecs].map((spec) => + ...[...validatorSpecs, ...faucetSpecs].map((spec) => Bun.write( join(directory, spec.name), `${JSON.stringify({ [spec.key]: spec.value }, null, 2)}\n` @@ -132,9 +129,8 @@ const outputToFile = async (payload: OutputPayload): Promise => { const outputToKubernetes = async (payload: OutputPayload): Promise => { const { client, namespace } = await createKubernetesClient(); - const validatorSpecs = createSpecsForGroup("validator", payload.validators); - const rpcSpecs = createSpecsForGroup("rpc-node", payload.rpcNodes); - const allSpecs = [...validatorSpecs, ...rpcSpecs]; + const validatorSpecs = createValidatorSpecs(payload.validators); + const allSpecs = [...validatorSpecs]; const configMapSpecs = [ ...allSpecs.filter((spec) => spec.key !== "privateKey"), ...createFaucetConfigSpecs(payload.faucet), @@ -159,15 +155,9 @@ const outputToKubernetes = async (payload: OutputPayload): Promise => { ); }; -const createSpecsForGroup = ( - group: "validator" | "rpc-node", - nodes: readonly IndexedNode[] -): ConfigMapSpec[] => { - const prefix = - group === "validator" ? "besu-node-validator" : "besu-node-rpc-node"; - - return nodes.flatMap((node) => { - const base = `${prefix}-${node.index}`; +const createValidatorSpecs = (nodes: readonly IndexedNode[]): ConfigMapSpec[] => + nodes.flatMap((node) => { + const base = `besu-node-validator-${node.index}`; return [ { name: `${base}-address`, key: "address", value: node.address }, { @@ -179,7 +169,6 @@ const createSpecsForGroup = ( { name: `${base}-pubkey`, key: "publicKey", value: node.publicKey }, ]; }); -}; const createFaucetConfigSpecs = (faucet: GeneratedNodeKey): ConfigMapSpec[] => [ { name: "besu-faucet-address", key: "address", value: faucet.address }, From 19ded5100b336e34b55c2cfbffafce3af95887c8 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 15:21:44 +0200 Subject: [PATCH 02/17] feat(cli): enhance README and Helm chart for network-bootstrapper - Updated README.md to include additional options for the generate command, detailing parameters for static nodes and consensus settings. - Refined Helm chart files for network-bootstrapper, ensuring accurate configuration for static node settings and genesis parameters. - Adjusted test cases in build-command.test.ts to align with new CLI options and expected outputs. --- charts/network/Chart.lock | 7 +- .../charts/network-bootstrapper/README.md | 5 +- .../network-bootstrapper/templates/job.yaml | 9 ++ .../charts/network-bootstrapper/values.yaml | 6 + charts/network/charts/network-nodes/README.md | 147 ++++++++++++++++++ .../charts/network-nodes/templates/NOTES.txt | 35 ----- .../network-nodes/templates/configmap.yaml | 109 +++++++------ .../templates/statefulset-rpc.yaml | 25 ++- .../templates/statefulset-validator.yaml | 25 ++- .../network/charts/network-nodes/values.yaml | 4 +- src/cli/build-command.test.ts | 145 +++++++++++++++++ src/cli/build-command.ts | 82 ++++++++++ src/cli/output.test.ts | 48 +++++- src/cli/output.ts | 20 +++ 14 files changed, 538 insertions(+), 129 deletions(-) create mode 100644 charts/network/charts/network-nodes/README.md delete mode 100644 charts/network/charts/network-nodes/templates/NOTES.txt diff --git a/charts/network/Chart.lock b/charts/network/Chart.lock index 5e3ead6..ae917ed 100644 --- a/charts/network/Chart.lock +++ b/charts/network/Chart.lock @@ -2,5 +2,8 @@ dependencies: - name: network-bootstrapper repository: "" version: '*' -digest: sha256:dbed3aed691528082678797a03e8695a7e3857d059d09793ec6b1019277e836d -generated: "2025-09-17T13:10:16.197366+02:00" +- name: network-nodes + repository: "" + version: '*' +digest: sha256:858da5a469d81d0ce21b0661002958546cb645d7cd1851dd85404930905e5094 +generated: "2025-09-17T14:38:28.190498+02:00" diff --git a/charts/network/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md index 0dbb239..715c88d 100644 --- a/charts/network/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -35,7 +35,7 @@ A Helm chart for Kubernetes | serviceAccount.automount | bool | `true` | Automatically mount the service account token into the pod. | | serviceAccount.create | bool | `true` | Whether to create a service account automatically. | | serviceAccount.name | string | `""` | Existing service account name to use instead of one generated by the chart. If unset and `serviceAccount.create` is true, a name is derived from the chart fullname. | -| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","secondsPerBlock":null,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | +| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","secondsPerBlock":null,"staticNodeDiscoveryPort":30303,"staticNodeDomain":null,"staticNodePort":30303,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | | settings.allocations | string | `nil` | Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. | | settings.chainId | int | `nil` | Explicit chain ID applied to the genesis configuration. Defaults to a random value in the 40000-50000 range when omitted. | | settings.consensus | string | `nil` | Consensus engine to configure for the network (IBFTv2 or QBFT). Default: "QBFT". | @@ -45,6 +45,9 @@ A Helm chart for Kubernetes | settings.gasPrice | int | `nil` | Base gas price in wei applied to the chain. Default: 0. | | settings.outputType | string | `"kubernetes"` | Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". | | settings.secondsPerBlock | int | `nil` | Target block time in seconds encoded into genesis. Default: 2. | +| settings.staticNodeDiscoveryPort | int | `30303` | Discovery port used in static node enode URIs. Default: 30303. | +| settings.staticNodeDomain | string | `nil` | DNS suffix appended to static node hostnames (for example: "namespace.svc.cluster.local"). Default: null. | +| settings.staticNodePort | int | `30303` | P2P port used in static node enode URIs. Default: 30303. | | settings.validators | int | `nil` | Number of validator node definitions the bootstrapper generates. Default: 4. | | tolerations | list | `[]` | Kubernetes tolerations assigned to the bootstrapper pod. | | volumeMounts | list | `[]` | Additional volume mounts added to the bootstrapper container. | diff --git a/charts/network/charts/network-bootstrapper/templates/job.yaml b/charts/network/charts/network-bootstrapper/templates/job.yaml index 4171008..2485f7c 100644 --- a/charts/network/charts/network-bootstrapper/templates/job.yaml +++ b/charts/network/charts/network-bootstrapper/templates/job.yaml @@ -42,6 +42,15 @@ spec: {{- with .Values.settings.validators }} - --validators={{ . }} {{- end }} + {{- with .Values.settings.staticNodeDomain }} + - --static-node-domain={{ . }} + {{- end }} + {{- with .Values.settings.staticNodePort }} + - --static-node-port={{ . }} + {{- end }} + {{- with .Values.settings.staticNodeDiscoveryPort }} + - --static-node-discovery-port={{ . }} + {{- end }} {{- with .Values.settings.allocations }} - --allocations={{ . }} {{- end }} diff --git a/charts/network/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml index cef294d..d8dcb4a 100644 --- a/charts/network/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -91,6 +91,12 @@ affinity: {} settings: # -- (int) Number of validator node definitions the bootstrapper generates. Default: 4. validators: + # -- (string) DNS suffix appended to static node hostnames (for example: "namespace.svc.cluster.local"). Default: null. + staticNodeDomain: + # -- (int) P2P port used in static node enode URIs. Default: 30303. + staticNodePort: 30303 + # -- (int) Discovery port used in static node enode URIs. Default: 30303. + staticNodeDiscoveryPort: 30303 # -- (string) Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. allocations: # -- (string) Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". diff --git a/charts/network/charts/network-nodes/README.md b/charts/network/charts/network-nodes/README.md new file mode 100644 index 0000000..18500a7 --- /dev/null +++ b/charts/network/charts/network-nodes/README.md @@ -0,0 +1,147 @@ +# network-nodes + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) + +A Helm chart for Kubernetes + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| SettleMint | | | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| config.bonsaiLimitTrieLogsEnabled | bool | `false` | | +| config.cacheLastBlocks | int | `1024` | | +| config.dataStorageFormat | string | `"FOREST"` | | +| config.graphql.corsOrigins[0] | string | `"all"` | | +| config.graphql.enabled | bool | `true` | | +| config.graphql.host | string | `"0.0.0.0"` | | +| config.hostAllowlist[0] | string | `"*"` | | +| config.http.api[0] | string | `"DEBUG"` | | +| config.http.api[10] | string | `"PLUGINS"` | | +| config.http.api[1] | string | `"ETH"` | | +| config.http.api[2] | string | `"ADMIN"` | | +| config.http.api[3] | string | `"WEB3"` | | +| config.http.api[4] | string | `"IBFT"` | | +| config.http.api[5] | string | `"NET"` | | +| config.http.api[6] | string | `"TRACE"` | | +| config.http.api[7] | string | `"QBFT"` | | +| config.http.api[8] | string | `"PERM"` | | +| config.http.api[9] | string | `"TXPOOL"` | | +| config.http.authenticationEnabled | bool | `false` | | +| config.http.corsOrigins[0] | string | `"all"` | | +| config.http.enabled | bool | `true` | | +| config.http.host | string | `"0.0.0.0"` | | +| config.http.maxActiveConnections | int | `2000` | | +| config.http.maxBatchSize | int | `512` | | +| config.http.maxRequestContentLength | int | `524288000` | | +| config.logging | string | `"INFO"` | | +| config.metrics.categories[0] | string | `"BLOCKCHAIN"` | | +| config.metrics.categories[10] | string | `"TRANSACTION_POOL"` | | +| config.metrics.categories[1] | string | `"ETHEREUM"` | | +| config.metrics.categories[2] | string | `"EXECUTORS"` | | +| config.metrics.categories[3] | string | `"JVM"` | | +| config.metrics.categories[4] | string | `"NETWORK"` | | +| config.metrics.categories[5] | string | `"PEERS"` | | +| config.metrics.categories[6] | string | `"PROCESS"` | | +| config.metrics.categories[7] | string | `"PRUNER"` | | +| config.metrics.categories[8] | string | `"RPC"` | | +| config.metrics.categories[9] | string | `"SYNCHRONIZER"` | | +| config.metrics.enabled | bool | `true` | | +| config.metrics.host | string | `"0.0.0.0"` | | +| config.minGasPrice | int | `0` | | +| config.p2p.discoveryEnabled | bool | `true` | | +| config.p2p.enabled | bool | `true` | | +| config.p2p.interface | string | `"0.0.0.0"` | | +| config.p2p.maxPeers | int | `25` | | +| config.p2p.staticNodesFile | string | `"/etc/besu/static-nodes.json"` | | +| config.privateKeyFilename | string | `"privateKey"` | | +| config.randomPeerPriorityEnabled | bool | `true` | | +| config.receiptCompactionEnabled | bool | `true` | | +| config.remoteConnectionsLimitEnabled | bool | `false` | | +| config.revertReasonEnabled | bool | `true` | | +| config.rpc.txFeecap | int | `0` | | +| config.sync.minPeers | int | `1` | | +| config.sync.mode | string | `"FULL"` | | +| config.txPool.enableSaveRestore | bool | `true` | | +| config.txPool.limitByAccountPercentage | int | `1` | | +| config.txPool.maxSize | int | `100000` | | +| config.txPool.noLocalPriority | bool | `true` | | +| config.txPool.type | string | `"SEQUENCED"` | | +| config.ws.api[0] | string | `"DEBUG"` | | +| config.ws.api[10] | string | `"PLUGINS"` | | +| config.ws.api[1] | string | `"ETH"` | | +| config.ws.api[2] | string | `"ADMIN"` | | +| config.ws.api[3] | string | `"WEB3"` | | +| config.ws.api[4] | string | `"IBFT"` | | +| config.ws.api[5] | string | `"NET"` | | +| config.ws.api[6] | string | `"TRACE"` | | +| config.ws.api[7] | string | `"QBFT"` | | +| config.ws.api[8] | string | `"PERM"` | | +| config.ws.api[9] | string | `"TXPOOL"` | | +| config.ws.authenticationEnabled | bool | `false` | | +| config.ws.enabled | bool | `true` | | +| config.ws.host | string | `"0.0.0.0"` | | +| config.ws.maxActiveConnections | int | `2000` | | +| config.ws.maxFrameSize | int | `2097152` | | +| fullnameOverride | string | `""` | | +| httpRoute | object | `{"annotations":{},"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"gateway","sectionName":"http"}],"rules":[{"matches":[{"path":{"type":"PathPrefix","value":"/headers"}}]}]}` | Expose the service via gateway-api HTTPRoute Requires Gateway API resources and suitable controller installed within the cluster (see: https://gateway-api.sigs.k8s.io/guides/) | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"docker.io/hyperledger/besu"` | | +| image.tag | string | `"25.8.0"` | | +| imagePullSecrets | list | `[]` | | +| ingress.annotations | object | `{}` | | +| ingress.className | string | `""` | | +| ingress.enabled | bool | `false` | | +| ingress.hosts[0].host | string | `"chart-example.local"` | | +| ingress.hosts[0].paths[0].path | string | `"/"` | | +| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | +| ingress.tls | list | `[]` | | +| livenessProbe.httpGet.path | string | `"/"` | | +| livenessProbe.httpGet.port | string | `"http"` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| openShiftRoute | object | `{"alternateBackends":[],"annotations":{},"enabled":false,"host":"","path":"","port":{"targetPort":"http"},"tls":null,"to":{"weight":100},"wildcardPolicy":""}` | Expose the service via OpenShift Route when running on OpenShift clusters This relies on the OpenShift router to make the network nodes reachable externally. | +| persistence.accessModes[0] | string | `"ReadWriteOnce"` | | +| persistence.annotations | object | `{}` | | +| persistence.enabled | bool | `false` | | +| persistence.existingClaim | string | `""` | | +| persistence.mountPath | string | `"/data"` | | +| persistence.readOnly | bool | `false` | | +| persistence.retention.whenDeleted | string | `"Retain"` | | +| persistence.retention.whenScaled | string | `"Delete"` | | +| persistence.selector | object | `{}` | | +| persistence.size | string | `"20Gi"` | | +| persistence.storageClass | string | `""` | | +| persistence.subPath | string | `""` | | +| persistence.volumeMode | string | `""` | | +| persistence.volumeName | string | `"data"` | | +| podAnnotations | object | `{}` | | +| podLabels | object | `{}` | | +| podSecurityContext | object | `{}` | | +| readinessProbe.httpGet.path | string | `"/"` | | +| readinessProbe.httpGet.port | string | `"http"` | | +| resources | object | `{}` | | +| rpcReplicaCount | int | `2` | | +| securityContext | object | `{}` | | +| service.p2pType | string | `"NodePort"` | | +| service.ports.discovery | int | `30303` | | +| service.ports.graphql | int | `8547` | | +| service.ports.metrics | int | `9545` | | +| service.ports.rlpx | int | `30303` | | +| service.ports.rpc | int | `8545` | | +| service.ports.ws | int | `8546` | | +| service.type | string | `"ClusterIP"` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.automount | bool | `true` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `""` | | +| tolerations | list | `[]` | | +| validatorReplicaCount | int | `4` | | +| volumeMounts | list | `[]` | | +| volumes | list | `[]` | | diff --git a/charts/network/charts/network-nodes/templates/NOTES.txt b/charts/network/charts/network-nodes/templates/NOTES.txt deleted file mode 100644 index 6f0a610..0000000 --- a/charts/network/charts/network-nodes/templates/NOTES.txt +++ /dev/null @@ -1,35 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.httpRoute.enabled }} -{{- if .Values.httpRoute.hostnames }} - export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} -{{- else }} - export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") - {{- end }} -{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} - echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" - - NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. - The rules can be set for path, method, header and query parameters. - You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' -{{- end }} -{{- else if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nodes.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nodes.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nodes.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nodes.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/charts/network/charts/network-nodes/templates/configmap.yaml b/charts/network/charts/network-nodes/templates/configmap.yaml index 6f6aba8..1369dce 100644 --- a/charts/network/charts/network-nodes/templates/configmap.yaml +++ b/charts/network/charts/network-nodes/templates/configmap.yaml @@ -7,60 +7,57 @@ metadata: {{- $persistence := .Values.persistence | default (dict) }} {{- $persistenceEnabled := default false (get $persistence "enabled") }} {{- $mountPath := default "" (get $persistence "mountPath") }} -{{- $dataPath := default "/data" .Values.config.dataPath }} -{{- if and $persistenceEnabled (ne $mountPath "") }} -{{- $dataPath = $mountPath }} -{{- end }} +{{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }} data: - config.toml: | - genesis-file = "/etc/besu/genesis.json" - logging = "{{ .Values.config.logging }}" - node-private-key-file = "/etc/besu/privatekey" - data-storage-format = "{{ .Values.config.dataStorageFormat }}" - bonsai-limit-trie-logs-enabled = {{ .Values.config.bonsaiLimitTrieLogsEnabled }} - data-path = "{{ $dataPath }}" - cache-last-blocks = {{ .Values.config.cacheLastBlocks }} - min-gas-price = {{ .Values.config.minGasPrice }} - tx-pool = "{{ .Values.config.txPool.type }}" - tx-pool-max-size = {{ .Values.config.txPool.maxSize }} - tx-pool-no-local-priority = {{ .Values.config.txPool.noLocalPriority }} - tx-pool-limit-by-account-percentage = {{ .Values.config.txPool.limitByAccountPercentage }} - tx-pool-enable-save-restore = {{ .Values.config.txPool.enableSaveRestore }} - p2p-enabled = {{ .Values.config.p2p.enabled }} - discovery-enabled = {{ .Values.config.p2p.discoveryEnabled }} - static-nodes-file = "{{ .Values.config.p2p.staticNodesFile }}" - p2p-interface = "{{ .Values.config.p2p.interface }}" - p2p-port = {{ .Values.service.ports.rlpx }} - max-peers = {{ .Values.config.p2p.maxPeers }} - rpc-tx-feecap = {{ .Values.config.rpc.txFeecap }} - sync-min-peers = {{ .Values.config.sync.minPeers }} - sync-mode = "{{ .Values.config.sync.mode }}" - remote-connections-limit-enabled = {{ .Values.config.remoteConnectionsLimitEnabled }} - random-peer-priority-enabled = {{ .Values.config.randomPeerPriorityEnabled }} - receipt-compaction-enabled = {{ .Values.config.receiptCompactionEnabled }} - host-allowlist = {{ mustToJson .Values.config.hostAllowlist }} - rpc-http-enabled = {{ .Values.config.http.enabled }} - rpc-http-host = "{{ .Values.config.http.host }}" - rpc-http-port = {{ .Values.service.ports.rpc }} - rpc-http-api = {{ mustToJson .Values.config.http.api }} - rpc-http-cors-origins = {{ mustToJson .Values.config.http.corsOrigins }} - rpc-http-authentication-enabled = {{ .Values.config.http.authenticationEnabled }} - revert-reason-enabled = {{ .Values.config.revertReasonEnabled }} - rpc-http-max-active-connections = {{ .Values.config.http.maxActiveConnections }} - rpc-ws-max-active-connections = {{ .Values.config.ws.maxActiveConnections }} - rpc-http-max-request-content-length = {{ .Values.config.http.maxRequestContentLength }} - rpc-ws-max-frame-size = {{ .Values.config.ws.maxFrameSize }} - rpc-http-max-batch-size = {{ .Values.config.http.maxBatchSize }} - graphql-http-enabled = {{ .Values.config.graphql.enabled }} - graphql-http-host = "{{ .Values.config.graphql.host }}" - graphql-http-port = {{ .Values.service.ports.graphql }} - graphql-http-cors-origins = {{ mustToJson .Values.config.graphql.corsOrigins }} - rpc-ws-enabled = {{ .Values.config.ws.enabled }} - rpc-ws-host = "{{ .Values.config.ws.host }}" - rpc-ws-port = {{ .Values.service.ports.ws }} - rpc-ws-api = {{ mustToJson .Values.config.ws.api }} - rpc-ws-authentication-enabled = {{ .Values.config.ws.authenticationEnabled }} - metrics-enabled = {{ .Values.config.metrics.enabled }} - metrics-host = "{{ .Values.config.metrics.host }}" - metrics-port = {{ .Values.service.ports.metrics }} - metrics-category = {{ mustToJson .Values.config.metrics.categories }} + config.toml: |- + data-path="/data" + genesis-file="/etc/besu/genesis.json" + logging={{ .Values.config.logging | quote }} + node-private-key-file={{ printf "/data/%s" $privateKeyFilename | quote }} + data-storage-format={{ .Values.config.dataStorageFormat | quote }} + bonsai-limit-trie-logs-enabled={{ .Values.config.bonsaiLimitTrieLogsEnabled }} + cache-last-blocks={{ .Values.config.cacheLastBlocks | int }} + min-gas-price={{ .Values.config.minGasPrice | int }} + tx-pool={{ .Values.config.txPool.type | quote }} + tx-pool-max-size={{ .Values.config.txPool.maxSize | int }} + tx-pool-no-local-priority={{ .Values.config.txPool.noLocalPriority }} + tx-pool-limit-by-account-percentage={{ .Values.config.txPool.limitByAccountPercentage | int }} + tx-pool-enable-save-restore={{ .Values.config.txPool.enableSaveRestore }} + p2p-enabled={{ .Values.config.p2p.enabled }} + discovery-enabled={{ .Values.config.p2p.discoveryEnabled }} + static-nodes-file={{ .Values.config.p2p.staticNodesFile | quote }} + p2p-interface={{ .Values.config.p2p.interface | quote }} + p2p-port={{ .Values.service.ports.rlpx | int }} + max-peers={{ .Values.config.p2p.maxPeers | int }} + rpc-tx-feecap={{ .Values.config.rpc.txFeecap | int }} + sync-min-peers={{ .Values.config.sync.minPeers | int }} + sync-mode={{ .Values.config.sync.mode | quote }} + remote-connections-limit-enabled={{ .Values.config.remoteConnectionsLimitEnabled }} + random-peer-priority-enabled={{ .Values.config.randomPeerPriorityEnabled }} + receipt-compaction-enabled={{ .Values.config.receiptCompactionEnabled }} + host-allowlist={{ .Values.config.hostAllowlist | toJson }} + rpc-http-enabled={{ .Values.config.http.enabled }} + rpc-http-host={{ .Values.config.http.host | quote }} + rpc-http-port={{ .Values.service.ports.rpc | int }} + rpc-http-api={{ .Values.config.http.api | toJson }} + rpc-http-cors-origins={{ .Values.config.http.corsOrigins | toJson }} + rpc-http-authentication-enabled={{ .Values.config.http.authenticationEnabled }} + revert-reason-enabled={{ .Values.config.revertReasonEnabled }} + rpc-http-max-active-connections={{ .Values.config.http.maxActiveConnections | int }} + rpc-ws-max-active-connections={{ .Values.config.ws.maxActiveConnections | int }} + rpc-http-max-request-content-length={{ .Values.config.http.maxRequestContentLength | int }} + rpc-ws-max-frame-size={{ .Values.config.ws.maxFrameSize | int }} + rpc-http-max-batch-size={{ .Values.config.http.maxBatchSize | int }} + graphql-http-enabled={{ .Values.config.graphql.enabled }} + graphql-http-host={{ .Values.config.graphql.host | quote }} + graphql-http-port={{ .Values.service.ports.graphql | int }} + graphql-http-cors-origins={{ .Values.config.graphql.corsOrigins | toJson }} + rpc-ws-enabled={{ .Values.config.ws.enabled }} + rpc-ws-host={{ .Values.config.ws.host | quote }} + rpc-ws-port={{ .Values.service.ports.ws | int }} + rpc-ws-api={{ .Values.config.ws.api | toJson }} + rpc-ws-authentication-enabled={{ .Values.config.ws.authenticationEnabled }} + metrics-enabled={{ .Values.config.metrics.enabled }} + metrics-host={{ .Values.config.metrics.host | quote }} + metrics-port={{ .Values.service.ports.metrics | int }} + metrics-category={{ .Values.config.metrics.categories | toJson }} diff --git a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml index f8313fc..e08a820 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml @@ -24,7 +24,8 @@ spec: {{- $whenScaled := default "" (get $retention "whenScaled") }} {{- $useExistingClaim := and $persistenceEnabled (ne $existingClaim "") }} {{- $useClaimTemplate := and $persistenceEnabled (not $useExistingClaim) }} - {{- $shouldMountPersistence := and $persistenceEnabled (ne $mountPath "") }} + {{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }} + {{- $shouldMountPersistence := $persistenceEnabled }} {{- $rpcReplicaBudget := .Values.rpcReplicaCount | int }} replicas: {{ .Values.rpcReplicaCount }} serviceName: {{ include "nodes.fullname" . }} @@ -87,28 +88,22 @@ spec: --min-gas-price={{ .Values.config.minGasPrice }} ports: - name: json-rpc - port: {{ .Values.service.ports.rpc }} - targetPort: json-rpc + containerPort: {{ .Values.service.ports.rpc }} protocol: TCP - name: ws - port: {{ .Values.service.ports.ws }} - targetPort: ws + containerPort: {{ .Values.service.ports.ws }} protocol: TCP - name: graphql - port: {{ .Values.service.ports.graphql }} - targetPort: graphql + containerPort: {{ .Values.service.ports.graphql }} protocol: TCP - name: rlpx - port: {{ .Values.service.ports.rlpx }} - targetPort: rlpx + containerPort: {{ .Values.service.ports.rlpx }} protocol: TCP - name: discovery - port: {{ .Values.service.ports.discovery }} - targetPort: discovery + containerPort: {{ .Values.service.ports.discovery }} protocol: UDP - name: metrics - port: {{ .Values.service.ports.metrics }} - targetPort: metrics + containerPort: {{ .Values.service.ports.metrics }} protocol: TCP {{- with .Values.livenessProbe }} livenessProbe: @@ -133,12 +128,12 @@ spec: subPath: genesis.json readOnly: true - name: besu-private-key - mountPath: /etc/besu/privatekey + mountPath: {{ printf "/data/%s" $privateKeyFilename }} subPathExpr: "$(POD_NAME)/privateKey" readOnly: true {{- if $shouldMountPersistence }} - name: {{ $volumeName }} - mountPath: {{ $mountPath }} + mountPath: /data {{- if $subPath }} subPath: {{ $subPath }} {{- end }} diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml index 95d0a99..543c0d9 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -24,7 +24,8 @@ spec: {{- $whenScaled := default "" (get $retention "whenScaled") }} {{- $useExistingClaim := and $persistenceEnabled (ne $existingClaim "") }} {{- $useClaimTemplate := and $persistenceEnabled (not $useExistingClaim) }} - {{- $shouldMountPersistence := and $persistenceEnabled (ne $mountPath "") }} + {{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }} + {{- $shouldMountPersistence := $persistenceEnabled }} {{- $validatorReplicaBudget := .Values.validatorReplicaCount | int }} replicas: {{ .Values.validatorReplicaCount }} serviceName: {{ include "nodes.fullname" . }} @@ -87,28 +88,22 @@ spec: --min-gas-price={{ .Values.config.minGasPrice }} ports: - name: json-rpc - port: {{ .Values.service.ports.rpc }} - targetPort: json-rpc + containerPort: {{ .Values.service.ports.rpc }} protocol: TCP - name: ws - port: {{ .Values.service.ports.ws }} - targetPort: ws + containerPort: {{ .Values.service.ports.ws }} protocol: TCP - name: graphql - port: {{ .Values.service.ports.graphql }} - targetPort: graphql + containerPort: {{ .Values.service.ports.graphql }} protocol: TCP - name: rlpx - port: {{ .Values.service.ports.rlpx }} - targetPort: rlpx + containerPort: {{ .Values.service.ports.rlpx }} protocol: TCP - name: discovery - port: {{ .Values.service.ports.discovery }} - targetPort: discovery + containerPort: {{ .Values.service.ports.discovery }} protocol: UDP - name: metrics - port: {{ .Values.service.ports.metrics }} - targetPort: metrics + containerPort: {{ .Values.service.ports.metrics }} protocol: TCP {{- with .Values.livenessProbe }} livenessProbe: @@ -133,12 +128,12 @@ spec: subPath: genesis.json readOnly: true - name: besu-private-key - mountPath: /etc/besu/privatekey + mountPath: {{ printf "/data/%s" $privateKeyFilename }} subPathExpr: "$(POD_NAME)/privateKey" readOnly: true {{- if $shouldMountPersistence }} - name: {{ $volumeName }} - mountPath: {{ $mountPath }} + mountPath: /data {{- if $subPath }} subPath: {{ $subPath }} {{- end }} diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index 812333b..17951ec 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -70,7 +70,7 @@ service: config: logging: INFO dataStorageFormat: FOREST - dataPath: /var/lib/besu + privateKeyFilename: privateKey bonsaiLimitTrieLogsEnabled: false cacheLastBlocks: 1024 minGasPrice: 0 @@ -263,7 +263,7 @@ readinessProbe: persistence: enabled: false # When enabled without an existingClaim, a PersistentVolumeClaim is created per replica. - mountPath: /var/lib/besu + mountPath: /data volumeName: data accessModes: - ReadWriteOnce diff --git a/src/cli/build-command.test.ts b/src/cli/build-command.test.ts index 0a30086..c9295c7 100644 --- a/src/cli/build-command.test.ts +++ b/src/cli/build-command.test.ts @@ -13,6 +13,9 @@ const VALIDATOR_LABEL = "validator nodes"; const VALIDATOR_RETURN = 2; const GENESIS_MARKER = '"extraData": "0xextra"'; const EXPECTED_DEFAULT_VALIDATOR = 4; +const DEFAULT_STATIC_NODE_PORT = 30_303; +const CUSTOM_STATIC_NODE_PORT = 40_000; +const LEADING_DOT_REGEX = /^\./u; const HEX_RADIX = 16; const PAD_WIDTH = 2; const PAD_CHAR = "0"; @@ -46,6 +49,30 @@ const expectedAddress = (index: number) => { return getAddress(`0x${pattern.repeat(ADDRESS_REPEAT)}`); }; +const expectedPublicKey = (index: number) => { + const pattern = index.toString(HEX_RADIX).padStart(PAD_WIDTH, PAD_CHAR); + return `0x04${pattern.repeat(PUBLIC_KEY_REPEAT)}` as const; +}; + +const expectedStaticNodeUri = ( + index: number, + domain?: string, + port: number = DEFAULT_STATIC_NODE_PORT, + discoveryPort: number = DEFAULT_STATIC_NODE_PORT +): string => { + const normalizedDomain = + domain === undefined || domain.trim().length === 0 + ? undefined + : domain.trim().replace(LEADING_DOT_REGEX, ""); + const podName = `besu-node-validator-${index}-0`; + const serviceName = `besu-node-validator-${index}`; + const host = normalizedDomain + ? `${podName}.${serviceName}.${normalizedDomain}` + : podName; + const publicKey = expectedPublicKey(index).slice(2); + return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; +}; + const captureStdout = () => { let captured = ""; const original = process.stdout.write; @@ -152,9 +179,14 @@ describe("CLI command bootstrap", () => { const output = stdout.read(); expect(output).toContain("Genesis"); expect(output).toContain("Validator Nodes"); + expect(output).toContain("Static Nodes"); expect(output).toContain(GENESIS_MARKER); expect(loadAllocationsPath).toBe("/tmp/alloc.json"); expect(outputInvocation?.type).toBe("screen"); + expect(outputInvocation?.payload.staticNodes).toEqual([ + expectedStaticNodeUri(FIRST_VALIDATOR_INDEX), + expectedStaticNodeUri(SECOND_VALIDATOR_INDEX), + ]); }); test("createCliCommand wires metadata", () => { @@ -225,6 +257,12 @@ describe("CLI command bootstrap", () => { "2048", "--contract-size-limit", "10000", + "--static-node-domain", + "network.svc.cluster.local", + "--static-node-port", + `${CUSTOM_STATIC_NODE_PORT}`, + "--static-node-discovery-port", + "0", ], { from: "node" } ); @@ -234,6 +272,113 @@ describe("CLI command bootstrap", () => { expect(stdout.read()).toContain(GENESIS_MARKER); }); + test("createCliCommand accepts static node configuration flags", async () => { + const factory = createFactoryStub(); + let capturedPayload: OutputPayload | undefined; + + const deps: BootstrapDependencies = { + factory, + promptForCount: (_label, provided, defaultValue) => + Promise.resolve(provided ?? defaultValue), + promptForGenesis: (_service, { faucetAddress }) => + Promise.resolve({ + algorithm: ALGORITHM.QBFT, + config: { + chainId: 77, + faucetWalletAddress: faucetAddress, + gasLimit: "0x1", + secondsPerBlock: 2, + }, + genesis: { config: {}, extraData: "0xextra" } as any, + }), + service: {} as any, + loadAllocations: () => + Promise.resolve({} satisfies Record), + outputResult: (_type, payload) => { + capturedPayload = payload; + return Promise.resolve(); + }, + }; + + const command = createCliCommand(deps); + await command.parseAsync( + [ + "node", + "cli", + "generate", + "--validators", + "1", + "--static-node-domain", + "network.svc.cluster.local", + "--static-node-port", + "40000", + "--static-node-discovery-port", + "0", + ], + { from: "node" } + ); + + expect(capturedPayload?.staticNodes).toEqual([ + expectedStaticNodeUri( + 1, + "network.svc.cluster.local", + CUSTOM_STATIC_NODE_PORT, + 0 + ), + ]); + }); + + test("runBootstrap builds static nodes with domain and custom ports", async () => { + const factory = createFactoryStub(); + let capturedPayload: OutputPayload | undefined; + + const deps: BootstrapDependencies = { + factory, + promptForCount: (_label, provided, defaultValue) => + Promise.resolve(provided ?? defaultValue), + promptForGenesis: (_service, { validatorAddresses, faucetAddress }) => { + expect(validatorAddresses).toHaveLength(1); + expect(faucetAddress).toBe(expectedAddress(2)); + return Promise.resolve({ + algorithm: ALGORITHM.QBFT, + config: { + chainId: 123, + faucetWalletAddress: faucetAddress, + gasLimit: "0x1", + secondsPerBlock: 2, + }, + genesis: { config: {}, extraData: "0xextra" } as any, + }); + }, + service: {} as any, + loadAllocations: () => + Promise.resolve({} satisfies Record), + outputResult: (_type, payload) => { + capturedPayload = payload; + return Promise.resolve(); + }, + }; + + await runBootstrap( + { + validators: 1, + staticNodeDomain: "network.svc.cluster.local", + staticNodePort: CUSTOM_STATIC_NODE_PORT, + staticNodeDiscoveryPort: 0, + }, + deps + ); + + expect(capturedPayload?.staticNodes).toEqual([ + expectedStaticNodeUri( + 1, + "network.svc.cluster.local", + CUSTOM_STATIC_NODE_PORT, + 0 + ), + ]); + }); + test("runBootstrap bypasses genesis prompts when CLI overrides provided", async () => { const factory = createFactoryStub(); const validatorOverride = 1; diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 2250c23..204be8a 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -28,6 +28,9 @@ type CliOptions = { validators?: number; outputType?: OutputType; secondsPerBlock?: number; + staticNodeDomain?: string; + staticNodePort?: number; + staticNodeDiscoveryPort?: number; }; type BootstrapDependencies = { @@ -40,7 +43,9 @@ type BootstrapDependencies = { }; const DEFAULT_VALIDATOR_COUNT = 4; +const DEFAULT_STATIC_NODE_PORT = 30_303; const OUTPUT_CHOICES: OutputType[] = ["screen", "file", "kubernetes"]; +const LEADING_DOT_REGEX = /^\./u; // Normalizes CLI inputs wrapped by orchestrators that keep literal quotes. const stripSurroundingQuotes = (value: string): string => { @@ -94,6 +99,41 @@ const generateGroup = (factory: NodeKeyFactory, count: number): IndexedNode[] => ...factory.generate(), })); +const normalizeStaticNodeDomain = ( + domain: string | undefined +): string | undefined => { + if (!domain) { + return; + } + + const trimmed = domain.trim().replace(LEADING_DOT_REGEX, ""); + return trimmed.length === 0 ? undefined : trimmed; +}; + +const createStaticNodeEntries = ( + nodes: readonly IndexedNode[], + { + domain, + port, + discoveryPort, + }: { domain?: string; port: number; discoveryPort: number } +): string[] => { + const normalizedDomain = normalizeStaticNodeDomain(domain); + + return nodes.map((node) => { + const podName = `besu-node-validator-${node.index}-0`; + const serviceName = `besu-node-validator-${node.index}`; + const host = normalizedDomain + ? `${podName}.${serviceName}.${normalizedDomain}` + : podName; + const publicKey = node.publicKey.startsWith("0x") + ? node.publicKey.slice(2) + : node.publicKey; + + return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; + }); +}; + const runBootstrap = async ( options: CliOptions, deps: BootstrapDependencies @@ -110,6 +150,9 @@ const runBootstrap = async ( outputType, secondsPerBlock, validators: validatorOption, + staticNodeDomain: staticNodeDomainOption, + staticNodePort: staticNodePortOption, + staticNodeDiscoveryPort: staticNodeDiscoveryPortOption, } = options; const resolveCount = ( @@ -134,6 +177,11 @@ const runBootstrap = async ( const validators = generateGroup(deps.factory, validatorsCount); const faucet = deps.factory.generate(); + const staticNodes = createStaticNodeEntries(validators, { + domain: staticNodeDomainOption, + port: staticNodePortOption ?? DEFAULT_STATIC_NODE_PORT, + discoveryPort: staticNodeDiscoveryPortOption ?? DEFAULT_STATIC_NODE_PORT, + }); const validatorAddresses = validators.map((node) => node.address); @@ -163,6 +211,7 @@ const runBootstrap = async ( faucet, genesis, validators, + staticNodes, }; await deps.outputResult(outputType ?? "screen", payload); @@ -220,6 +269,24 @@ const createCliCommand = ( }, "screen" ) + .option( + "--static-node-domain ", + "DNS suffix appended to validator peer hostnames for static-nodes entries.", + (value: string) => stripSurroundingQuotes(value) + ) + .option( + "--static-node-port ", + "P2P port used for static-nodes enode URIs.", + (value: string) => parsePositiveInteger(value, "Static node port"), + DEFAULT_STATIC_NODE_PORT + ) + .option( + "--static-node-discovery-port ", + "Discovery port used for static-nodes enode URIs.", + (value: string) => + parseNonNegativeInteger(value, "Static node discovery port"), + DEFAULT_STATIC_NODE_PORT + ) .option( "--consensus ", `Consensus algorithm (${Object.values(ALGORITHM).join(", ")}). (default: ${ @@ -281,6 +348,18 @@ const createCliCommand = ( cmd.getOptionValueSource("validators") === "default" ? undefined : options.validators, + staticNodeDomain: + cmd.getOptionValueSource("staticNodeDomain") === "default" + ? undefined + : options.staticNodeDomain, + staticNodePort: + cmd.getOptionValueSource("staticNodePort") === "default" + ? undefined + : options.staticNodePort, + staticNodeDiscoveryPort: + cmd.getOptionValueSource("staticNodeDiscoveryPort") === "default" + ? undefined + : options.staticNodeDiscoveryPort, }; const sanitizedOptions: CliOptions = { @@ -289,6 +368,9 @@ const createCliCommand = ( normalizedOptions.allocations === undefined ? undefined : stripSurroundingQuotes(normalizedOptions.allocations), + staticNodeDomain: normalizeStaticNodeDomain( + normalizedOptions.staticNodeDomain + ), }; await runBootstrap(sanitizedOptions, deps); diff --git a/src/cli/output.test.ts b/src/cli/output.test.ts index 7312400..f95166e 100644 --- a/src/cli/output.test.ts +++ b/src/cli/output.test.ts @@ -76,13 +76,14 @@ const PUBLIC_KEY_HEX_LENGTH = 128; const HEX_RADIX = 16; const SAMPLE_VALIDATOR_INDEX = 1; const SAMPLE_FAUCET_INDEX = 99; -const EXPECTED_CONFIGMAP_COUNT = 6; +const EXPECTED_CONFIGMAP_COUNT = 7; const EXPECTED_SECRET_COUNT = 2; const HEX_PREFIX_PATTERN = /^0x/; const TEST_CHAIN_ID = 1; const HTTP_CONFLICT_STATUS = 409; const HTTP_INTERNAL_ERROR_STATUS = 500; const HTTP_SERVICE_UNAVAILABLE_STATUS = 503; +const LEADING_DOT_REGEX = /^\./u; const sampleNode = (index: number): IndexedNode => { const hexValue = index.toString(HEX_RADIX); @@ -99,10 +100,35 @@ const sampleNode = (index: number): IndexedNode => { }; }; +const staticNodeUri = ( + node: IndexedNode, + domain?: string, + port = 30_303, + discoveryPort = 30_303 +): string => { + const trimmedDomain = + domain === undefined || domain.trim().length === 0 + ? undefined + : domain.trim().replace(LEADING_DOT_REGEX, ""); + const podName = `besu-node-validator-${node.index}-0`; + const serviceName = `besu-node-validator-${node.index}`; + const host = trimmedDomain + ? `${podName}.${serviceName}.${trimmedDomain}` + : podName; + const publicKey = node.publicKey.startsWith("0x") + ? node.publicKey.slice(2) + : node.publicKey; + return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; +}; + +const sampleValidator = sampleNode(SAMPLE_VALIDATOR_INDEX); +const sampleFaucet = sampleNode(SAMPLE_FAUCET_INDEX); + const samplePayload: OutputPayload = { - faucet: sampleNode(SAMPLE_FAUCET_INDEX), + faucet: sampleFaucet, genesis: { config: { chainId: TEST_CHAIN_ID }, extraData: "0xabc" }, - validators: [sampleNode(SAMPLE_VALIDATOR_INDEX)], + validators: [sampleValidator], + staticNodes: [staticNodeUri(sampleValidator)], }; describe("outputResult", () => { @@ -111,6 +137,7 @@ describe("outputResult", () => { await outputResult("screen", samplePayload); expect(output).toContain("Genesis"); expect(output).toContain("Validator Nodes"); + expect(output).toContain("Static Nodes"); }); test("file output writes json artifacts", async () => { @@ -137,6 +164,7 @@ describe("outputResult", () => { "besu-node-validator-1-private-key", "besu-node-validator-1-pubkey", "genesis", + "static-nodes.json", ].sort() ); @@ -146,6 +174,12 @@ describe("outputResult", () => { ); expect(genesisContent).toContain(`"chainId": ${TEST_CHAIN_ID}`); + const staticNodesContent = await readFile( + join(targetDirPath, "static-nodes.json"), + "utf8" + ); + expect(JSON.parse(staticNodesContent)).toEqual(samplePayload.staticNodes); + await rm("out", { recursive: true, force: true }); }); @@ -231,6 +265,7 @@ describe("outputResult", () => { expect(mapNames).toContain("besu-genesis"); expect(mapNames).toContain("besu-faucet-address"); expect(mapNames).toContain("besu-faucet-pubkey"); + expect(mapNames).toContain("besu-static-nodes"); expect(mapNames).not.toContain("besu-faucet-enode"); const secretNames = createdSecrets.map((entry) => entry.name).sort(); expect(secretNames).toEqual([ @@ -241,6 +276,13 @@ describe("outputResult", () => { entry.name.endsWith("validator-1-private-key") ); expect(privateKeySecret?.data?.privateKey).toMatch(HEX_PREFIX_PATTERN); + const staticNodesConfig = createdConfigMaps.find( + (entry) => entry.name === "besu-static-nodes" + ); + expect(staticNodesConfig?.data?.["static-nodes.json"]).toBeDefined(); + expect( + JSON.parse(staticNodesConfig?.data?.["static-nodes.json"] ?? "[]") + ).toEqual(samplePayload.staticNodes); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; (KubeConfig.prototype as any).makeApiClient = originalMake; diff --git a/src/cli/output.ts b/src/cli/output.ts index 01d2e87..99f6c35 100644 --- a/src/cli/output.ts +++ b/src/cli/output.ts @@ -14,6 +14,7 @@ type OutputPayload = { faucet: GeneratedNodeKey; genesis: unknown; validators: readonly IndexedNode[]; + staticNodes: readonly string[]; }; type ConfigMapSpec = { @@ -46,6 +47,15 @@ const printGroup = (title: string, nodes: readonly IndexedNode[]): void => { process.stdout.write("\n"); }; +const printStaticNodes = (staticNodes: readonly string[]): void => { + if (staticNodes.length === 0) { + return; + } + + process.stdout.write(`${accent("Static Nodes")}\n`); + process.stdout.write(`${JSON.stringify(staticNodes, null, 2)}\n\n`); +}; + const printFaucet = (faucet: GeneratedNodeKey): void => { process.stdout.write(`${accent("Faucet Account")}\n`); process.stdout.write(` address: ${faucet.address}\n`); @@ -64,6 +74,7 @@ const outputToScreen = (payload: OutputPayload): void => { process.stdout.write("\n\n"); printGenesis("Genesis", genesisJson); printGroup("Validator Nodes", payload.validators); + printStaticNodes(payload.staticNodes); printFaucet(payload.faucet); }; @@ -121,6 +132,10 @@ const outputToFile = async (payload: OutputPayload): Promise => { `${JSON.stringify({ [spec.key]: spec.value }, null, 2)}\n` ) ), + Bun.write( + join(directory, "static-nodes.json"), + `${JSON.stringify(payload.staticNodes, null, 2)}\n` + ), ]; await Promise.all(writes); @@ -139,6 +154,11 @@ const outputToKubernetes = async (payload: OutputPayload): Promise => { key: "genesis.json", value: JSON.stringify(payload.genesis, null, 2), }, + { + name: "besu-static-nodes", + key: "static-nodes.json", + value: JSON.stringify(payload.staticNodes, null, 2), + }, ]; const secretSpecs = [ ...allSpecs.filter((spec) => spec.key === "privateKey"), From 29dd4b816d0aafe5580b52b171ec0fbc0432173f Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 15:48:58 +0200 Subject: [PATCH 03/17] feat(cli): update README and refine CLI options for static nodes - Enhanced README.md to include detailed descriptions of new CLI options for static node configuration, including domain, namespace, and ports. - Updated Helm chart files to ensure accurate representation of static node settings in the network-bootstrapper. - Adjusted test cases in build-command.test.ts to reflect the new CLI options and expected outputs. --- .../charts/network-bootstrapper/README.md | 12 +++-- .../network-bootstrapper/templates/job.yaml | 19 ++++---- .../charts/network-bootstrapper/values.yaml | 21 +++++++-- .../templates/statefulset-rpc.yaml | 7 +++ .../templates/statefulset-validator.yaml | 7 +++ src/cli/build-command.test.ts | 35 ++++++++++---- src/cli/build-command.ts | 46 +++++++++++++++++-- src/cli/output.test.ts | 34 +++++++++++--- 8 files changed, 144 insertions(+), 37 deletions(-) diff --git a/charts/network/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md index 715c88d..c0573fe 100644 --- a/charts/network/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -15,6 +15,9 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | Affinity and anti-affinity rules influencing pod placement. | +| clusterDomain | string | `"svc.cluster.local"` | Kubernetes cluster DNS suffix. Used when staticNodeDomain is not explicitly provided. | +| defaultStaticNodeDiscoveryPort | int | `30303` | Default discovery port for generated static node URIs when per-run settings are omitted. | +| defaultStaticNodePort | int | `30303` | Default P2P port for generated static node URIs when per-run settings are omitted. | | fullnameOverride | string | `""` | Fully qualified name override for resources created by this release. | | image | object | `{"pullPolicy":"IfNotPresent","repository":"ghcr.io/settlemint/network-bootstrapper","tag":""}` | Container image settings for the network bootstrapper workload. See https://kubernetes.io/docs/concepts/containers/images/ for background. | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy controlling when Kubernetes re-fetches the image layer manifest. | @@ -35,7 +38,7 @@ A Helm chart for Kubernetes | serviceAccount.automount | bool | `true` | Automatically mount the service account token into the pod. | | serviceAccount.create | bool | `true` | Whether to create a service account automatically. | | serviceAccount.name | string | `""` | Existing service account name to use instead of one generated by the chart. If unset and `serviceAccount.create` is true, a name is derived from the chart fullname. | -| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","secondsPerBlock":null,"staticNodeDiscoveryPort":30303,"staticNodeDomain":null,"staticNodePort":30303,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | +| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","secondsPerBlock":null,"staticNodeDiscoveryPort":null,"staticNodeDomain":null,"staticNodeNamespace":null,"staticNodePort":null,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | | settings.allocations | string | `nil` | Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. | | settings.chainId | int | `nil` | Explicit chain ID applied to the genesis configuration. Defaults to a random value in the 40000-50000 range when omitted. | | settings.consensus | string | `nil` | Consensus engine to configure for the network (IBFTv2 or QBFT). Default: "QBFT". | @@ -45,9 +48,10 @@ A Helm chart for Kubernetes | settings.gasPrice | int | `nil` | Base gas price in wei applied to the chain. Default: 0. | | settings.outputType | string | `"kubernetes"` | Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". | | settings.secondsPerBlock | int | `nil` | Target block time in seconds encoded into genesis. Default: 2. | -| settings.staticNodeDiscoveryPort | int | `30303` | Discovery port used in static node enode URIs. Default: 30303. | -| settings.staticNodeDomain | string | `nil` | DNS suffix appended to static node hostnames (for example: "namespace.svc.cluster.local"). Default: null. | -| settings.staticNodePort | int | `30303` | P2P port used in static node enode URIs. Default: 30303. | +| settings.staticNodeDiscoveryPort | int | `nil` | Discovery port used in static node enode URIs. Default: `defaultStaticNodeDiscoveryPort`. | +| settings.staticNodeDomain | string | `nil` | DNS suffix appended to static node hostnames (for example: "svc.cluster.local"). Default: `clusterDomain`. | +| settings.staticNodeNamespace | string | `nil` | Namespace segment inserted between service and domain in static node hostnames. Default: release namespace. | +| settings.staticNodePort | int | `nil` | P2P port used in static node enode URIs. Default: `defaultStaticNodePort`. | | settings.validators | int | `nil` | Number of validator node definitions the bootstrapper generates. Default: 4. | | tolerations | list | `[]` | Kubernetes tolerations assigned to the bootstrapper pod. | | volumeMounts | list | `[]` | Additional volume mounts added to the bootstrapper container. | diff --git a/charts/network/charts/network-bootstrapper/templates/job.yaml b/charts/network/charts/network-bootstrapper/templates/job.yaml index 2485f7c..edf524d 100644 --- a/charts/network/charts/network-bootstrapper/templates/job.yaml +++ b/charts/network/charts/network-bootstrapper/templates/job.yaml @@ -39,18 +39,21 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} args: - generate + {{- $clusterDomain := default "svc.cluster.local" .Values.clusterDomain }} + {{- $defaultStaticPort := default 30303 .Values.defaultStaticNodePort }} + {{- $defaultStaticDiscovery := default 30303 .Values.defaultStaticNodeDiscoveryPort }} + {{- $resolvedStaticDomain := default $clusterDomain .Values.settings.staticNodeDomain }} + {{- $resolvedStaticPort := default $defaultStaticPort .Values.settings.staticNodePort }} + {{- $resolvedStaticDiscovery := default $defaultStaticDiscovery .Values.settings.staticNodeDiscoveryPort }} {{- with .Values.settings.validators }} - --validators={{ . }} {{- end }} - {{- with .Values.settings.staticNodeDomain }} - - --static-node-domain={{ . }} - {{- end }} - {{- with .Values.settings.staticNodePort }} - - --static-node-port={{ . }} - {{- end }} - {{- with .Values.settings.staticNodeDiscoveryPort }} - - --static-node-discovery-port={{ . }} + - --static-node-namespace={{ default .Release.Namespace .Values.settings.staticNodeNamespace }} + {{- if $resolvedStaticDomain }} + - --static-node-domain={{ $resolvedStaticDomain }} {{- end }} + - --static-node-port={{ $resolvedStaticPort }} + - --static-node-discovery-port={{ $resolvedStaticDiscovery }} {{- with .Values.settings.allocations }} - --allocations={{ . }} {{- end }} diff --git a/charts/network/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml index d8dcb4a..78f1342 100644 --- a/charts/network/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -87,16 +87,27 @@ tolerations: [] # -- (object) Affinity and anti-affinity rules influencing pod placement. affinity: {} +# -- (string) Kubernetes cluster DNS suffix. Used when staticNodeDomain is not explicitly provided. +clusterDomain: svc.cluster.local + +# -- (int) Default P2P port for generated static node URIs when per-run settings are omitted. +defaultStaticNodePort: 30303 + +# -- (int) Default discovery port for generated static node URIs when per-run settings are omitted. +defaultStaticNodeDiscoveryPort: 30303 + # -- (object) Network bootstrapper CLI settings translated into command-line flags. settings: # -- (int) Number of validator node definitions the bootstrapper generates. Default: 4. validators: - # -- (string) DNS suffix appended to static node hostnames (for example: "namespace.svc.cluster.local"). Default: null. + # -- (string) DNS suffix appended to static node hostnames (for example: "svc.cluster.local"). Default: `clusterDomain`. staticNodeDomain: - # -- (int) P2P port used in static node enode URIs. Default: 30303. - staticNodePort: 30303 - # -- (int) Discovery port used in static node enode URIs. Default: 30303. - staticNodeDiscoveryPort: 30303 + # -- (string) Namespace segment inserted between service and domain in static node hostnames. Default: release namespace. + staticNodeNamespace: + # -- (int) P2P port used in static node enode URIs. Default: `defaultStaticNodePort`. + staticNodePort: + # -- (int) Discovery port used in static node enode URIs. Default: `defaultStaticNodeDiscoveryPort`. + staticNodeDiscoveryPort: # -- (string) Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. allocations: # -- (string) Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". diff --git a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml index e08a820..71825e3 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml @@ -127,6 +127,10 @@ spec: mountPath: /etc/besu/genesis.json subPath: genesis.json readOnly: true + - name: besu-static-nodes + mountPath: /etc/besu/static-nodes.json + subPath: static-nodes.json + readOnly: true - name: besu-private-key mountPath: {{ printf "/data/%s" $privateKeyFilename }} subPathExpr: "$(POD_NAME)/privateKey" @@ -152,6 +156,9 @@ spec: - name: besu-genesis configMap: name: besu-genesis + - name: besu-static-nodes + configMap: + name: besu-static-nodes - name: besu-private-key projected: {{- if gt $rpcReplicaBudget 0 }} diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml index 543c0d9..1622f71 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -127,6 +127,10 @@ spec: mountPath: /etc/besu/genesis.json subPath: genesis.json readOnly: true + - name: besu-static-nodes + mountPath: /etc/besu/static-nodes.json + subPath: static-nodes.json + readOnly: true - name: besu-private-key mountPath: {{ printf "/data/%s" $privateKeyFilename }} subPathExpr: "$(POD_NAME)/privateKey" @@ -152,6 +156,9 @@ spec: - name: besu-genesis configMap: name: besu-genesis + - name: besu-static-nodes + configMap: + name: besu-static-nodes - name: besu-private-key projected: {{- if gt $validatorReplicaBudget 0 }} diff --git a/src/cli/build-command.test.ts b/src/cli/build-command.test.ts index c9295c7..b7d8102 100644 --- a/src/cli/build-command.test.ts +++ b/src/cli/build-command.test.ts @@ -58,17 +58,27 @@ const expectedStaticNodeUri = ( index: number, domain?: string, port: number = DEFAULT_STATIC_NODE_PORT, - discoveryPort: number = DEFAULT_STATIC_NODE_PORT + discoveryPort: number = DEFAULT_STATIC_NODE_PORT, + namespace?: string ): string => { const normalizedDomain = domain === undefined || domain.trim().length === 0 ? undefined : domain.trim().replace(LEADING_DOT_REGEX, ""); + const normalizedNamespace = + namespace === undefined || namespace.trim().length === 0 + ? undefined + : namespace.trim(); const podName = `besu-node-validator-${index}-0`; const serviceName = `besu-node-validator-${index}`; - const host = normalizedDomain - ? `${podName}.${serviceName}.${normalizedDomain}` - : podName; + const segments = [podName, serviceName]; + if (normalizedNamespace) { + segments.push(normalizedNamespace); + } + if (normalizedDomain) { + segments.push(normalizedDomain); + } + const host = segments.join("."); const publicKey = expectedPublicKey(index).slice(2); return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; }; @@ -309,7 +319,9 @@ describe("CLI command bootstrap", () => { "--validators", "1", "--static-node-domain", - "network.svc.cluster.local", + "svc.cluster.local", + "--static-node-namespace", + "network", "--static-node-port", "40000", "--static-node-discovery-port", @@ -321,9 +333,10 @@ describe("CLI command bootstrap", () => { expect(capturedPayload?.staticNodes).toEqual([ expectedStaticNodeUri( 1, - "network.svc.cluster.local", + "svc.cluster.local", CUSTOM_STATIC_NODE_PORT, - 0 + 0, + "network" ), ]); }); @@ -362,7 +375,8 @@ describe("CLI command bootstrap", () => { await runBootstrap( { validators: 1, - staticNodeDomain: "network.svc.cluster.local", + staticNodeDomain: "svc.cluster.local", + staticNodeNamespace: "network", staticNodePort: CUSTOM_STATIC_NODE_PORT, staticNodeDiscoveryPort: 0, }, @@ -372,9 +386,10 @@ describe("CLI command bootstrap", () => { expect(capturedPayload?.staticNodes).toEqual([ expectedStaticNodeUri( 1, - "network.svc.cluster.local", + "svc.cluster.local", CUSTOM_STATIC_NODE_PORT, - 0 + 0, + "network" ), ]); }); diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 204be8a..0e85f20 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -29,6 +29,7 @@ type CliOptions = { outputType?: OutputType; secondsPerBlock?: number; staticNodeDomain?: string; + staticNodeNamespace?: string; staticNodePort?: number; staticNodeDiscoveryPort?: number; }; @@ -110,22 +111,45 @@ const normalizeStaticNodeDomain = ( return trimmed.length === 0 ? undefined : trimmed; }; +const normalizeStaticNodeNamespace = ( + namespace: string | undefined +): string | undefined => { + if (!namespace) { + return; + } + + const trimmed = namespace.trim(); + return trimmed.length === 0 ? undefined : trimmed; +}; + const createStaticNodeEntries = ( nodes: readonly IndexedNode[], { + namespace, domain, port, discoveryPort, - }: { domain?: string; port: number; discoveryPort: number } + }: { + namespace?: string; + domain?: string; + port: number; + discoveryPort: number; + } ): string[] => { const normalizedDomain = normalizeStaticNodeDomain(domain); + const normalizedNamespace = normalizeStaticNodeNamespace(namespace); return nodes.map((node) => { const podName = `besu-node-validator-${node.index}-0`; const serviceName = `besu-node-validator-${node.index}`; - const host = normalizedDomain - ? `${podName}.${serviceName}.${normalizedDomain}` - : podName; + const segments = [podName, serviceName]; + if (normalizedNamespace) { + segments.push(normalizedNamespace); + } + if (normalizedDomain) { + segments.push(normalizedDomain); + } + const host = segments.join("."); const publicKey = node.publicKey.startsWith("0x") ? node.publicKey.slice(2) : node.publicKey; @@ -151,6 +175,7 @@ const runBootstrap = async ( secondsPerBlock, validators: validatorOption, staticNodeDomain: staticNodeDomainOption, + staticNodeNamespace: staticNodeNamespaceOption, staticNodePort: staticNodePortOption, staticNodeDiscoveryPort: staticNodeDiscoveryPortOption, } = options; @@ -178,6 +203,7 @@ const runBootstrap = async ( const validators = generateGroup(deps.factory, validatorsCount); const faucet = deps.factory.generate(); const staticNodes = createStaticNodeEntries(validators, { + namespace: staticNodeNamespaceOption, domain: staticNodeDomainOption, port: staticNodePortOption ?? DEFAULT_STATIC_NODE_PORT, discoveryPort: staticNodeDiscoveryPortOption ?? DEFAULT_STATIC_NODE_PORT, @@ -274,6 +300,11 @@ const createCliCommand = ( "DNS suffix appended to validator peer hostnames for static-nodes entries.", (value: string) => stripSurroundingQuotes(value) ) + .option( + "--static-node-namespace ", + "Namespace segment inserted between service name and domain for static-nodes entries.", + (value: string) => stripSurroundingQuotes(value) + ) .option( "--static-node-port ", "P2P port used for static-nodes enode URIs.", @@ -352,6 +383,10 @@ const createCliCommand = ( cmd.getOptionValueSource("staticNodeDomain") === "default" ? undefined : options.staticNodeDomain, + staticNodeNamespace: + cmd.getOptionValueSource("staticNodeNamespace") === "default" + ? undefined + : options.staticNodeNamespace, staticNodePort: cmd.getOptionValueSource("staticNodePort") === "default" ? undefined @@ -371,6 +406,9 @@ const createCliCommand = ( staticNodeDomain: normalizeStaticNodeDomain( normalizedOptions.staticNodeDomain ), + staticNodeNamespace: normalizeStaticNodeNamespace( + normalizedOptions.staticNodeNamespace + ), }; await runBootstrap(sanitizedOptions, deps); diff --git a/src/cli/output.test.ts b/src/cli/output.test.ts index f95166e..74f1633 100644 --- a/src/cli/output.test.ts +++ b/src/cli/output.test.ts @@ -84,6 +84,10 @@ const HTTP_CONFLICT_STATUS = 409; const HTTP_INTERNAL_ERROR_STATUS = 500; const HTTP_SERVICE_UNAVAILABLE_STATUS = 503; const LEADING_DOT_REGEX = /^\./u; +const DEFAULT_STATIC_NODE_PORT = 30_303; +const DEFAULT_STATIC_NODE_DISCOVERY_PORT = 30_303; +const SAMPLE_STATIC_DOMAIN = "svc.cluster.local"; +const SAMPLE_STATIC_NAMESPACE = "network"; const sampleNode = (index: number): IndexedNode => { const hexValue = index.toString(HEX_RADIX); @@ -103,18 +107,28 @@ const sampleNode = (index: number): IndexedNode => { const staticNodeUri = ( node: IndexedNode, domain?: string, - port = 30_303, - discoveryPort = 30_303 + port = DEFAULT_STATIC_NODE_PORT, + discoveryPort = DEFAULT_STATIC_NODE_DISCOVERY_PORT, + namespace?: string ): string => { const trimmedDomain = domain === undefined || domain.trim().length === 0 ? undefined : domain.trim().replace(LEADING_DOT_REGEX, ""); + const trimmedNamespace = + namespace === undefined || namespace.trim().length === 0 + ? undefined + : namespace.trim(); const podName = `besu-node-validator-${node.index}-0`; const serviceName = `besu-node-validator-${node.index}`; - const host = trimmedDomain - ? `${podName}.${serviceName}.${trimmedDomain}` - : podName; + const segments = [podName, serviceName]; + if (trimmedNamespace) { + segments.push(trimmedNamespace); + } + if (trimmedDomain) { + segments.push(trimmedDomain); + } + const host = segments.join("."); const publicKey = node.publicKey.startsWith("0x") ? node.publicKey.slice(2) : node.publicKey; @@ -128,7 +142,15 @@ const samplePayload: OutputPayload = { faucet: sampleFaucet, genesis: { config: { chainId: TEST_CHAIN_ID }, extraData: "0xabc" }, validators: [sampleValidator], - staticNodes: [staticNodeUri(sampleValidator)], + staticNodes: [ + staticNodeUri( + sampleValidator, + SAMPLE_STATIC_DOMAIN, + DEFAULT_STATIC_NODE_PORT, + DEFAULT_STATIC_NODE_DISCOVERY_PORT, + SAMPLE_STATIC_NAMESPACE + ), + ], }; describe("outputResult", () => { From 1b4fefc8e5702f555006d8ba3d851d29750ee14d Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 15:57:14 +0200 Subject: [PATCH 04/17] fix(cli): refine node ID derivation and update related tests - Updated the `deriveNodeId` function to correctly handle public key prefixes and lengths. - Adjusted test cases in `build-command.test.ts` and `output.test.ts` to reflect changes in node ID derivation logic. - Ensured consistency in handling public keys across CLI commands. --- src/cli/build-command.test.ts | 9 ++++++++- src/cli/build-command.ts | 19 +++++++++++++++---- src/cli/output.test.ts | 9 ++++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/cli/build-command.test.ts b/src/cli/build-command.test.ts index b7d8102..9155499 100644 --- a/src/cli/build-command.test.ts +++ b/src/cli/build-command.test.ts @@ -16,6 +16,8 @@ const EXPECTED_DEFAULT_VALIDATOR = 4; const DEFAULT_STATIC_NODE_PORT = 30_303; const CUSTOM_STATIC_NODE_PORT = 40_000; const LEADING_DOT_REGEX = /^\./u; +const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04"; +const UNCOMPRESSED_PUBLIC_KEY_LENGTH = 130; const HEX_RADIX = 16; const PAD_WIDTH = 2; const PAD_CHAR = "0"; @@ -80,7 +82,12 @@ const expectedStaticNodeUri = ( } const host = segments.join("."); const publicKey = expectedPublicKey(index).slice(2); - return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; + const nodeId = + publicKey.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) && + publicKey.length === UNCOMPRESSED_PUBLIC_KEY_LENGTH + ? publicKey.slice(2) + : publicKey; + return `enode://${nodeId}@${host}:${port}?discport=${discoveryPort}`; }; const captureStdout = () => { diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 0e85f20..0d29c70 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -47,6 +47,8 @@ const DEFAULT_VALIDATOR_COUNT = 4; const DEFAULT_STATIC_NODE_PORT = 30_303; const OUTPUT_CHOICES: OutputType[] = ["screen", "file", "kubernetes"]; const LEADING_DOT_REGEX = /^\./u; +const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04"; +const UNCOMPRESSED_PUBLIC_KEY_LENGTH = 130; // Normalizes CLI inputs wrapped by orchestrators that keep literal quotes. const stripSurroundingQuotes = (value: string): string => { @@ -122,6 +124,17 @@ const normalizeStaticNodeNamespace = ( return trimmed.length === 0 ? undefined : trimmed; }; +const deriveNodeId = (publicKey: string): string => { + const trimmed = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey; + if ( + trimmed.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) && + trimmed.length === UNCOMPRESSED_PUBLIC_KEY_LENGTH + ) { + return trimmed.slice(2); + } + return trimmed; +}; + const createStaticNodeEntries = ( nodes: readonly IndexedNode[], { @@ -150,11 +163,9 @@ const createStaticNodeEntries = ( segments.push(normalizedDomain); } const host = segments.join("."); - const publicKey = node.publicKey.startsWith("0x") - ? node.publicKey.slice(2) - : node.publicKey; + const nodeId = deriveNodeId(node.publicKey); - return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; + return `enode://${nodeId}@${host}:${port}?discport=${discoveryPort}`; }); }; diff --git a/src/cli/output.test.ts b/src/cli/output.test.ts index 74f1633..d9f9449 100644 --- a/src/cli/output.test.ts +++ b/src/cli/output.test.ts @@ -88,6 +88,8 @@ const DEFAULT_STATIC_NODE_PORT = 30_303; const DEFAULT_STATIC_NODE_DISCOVERY_PORT = 30_303; const SAMPLE_STATIC_DOMAIN = "svc.cluster.local"; const SAMPLE_STATIC_NAMESPACE = "network"; +const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04"; +const UNCOMPRESSED_PUBLIC_KEY_LENGTH = 130; const sampleNode = (index: number): IndexedNode => { const hexValue = index.toString(HEX_RADIX); @@ -132,7 +134,12 @@ const staticNodeUri = ( const publicKey = node.publicKey.startsWith("0x") ? node.publicKey.slice(2) : node.publicKey; - return `enode://${publicKey}@${host}:${port}?discport=${discoveryPort}`; + const nodeId = + publicKey.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) && + publicKey.length === UNCOMPRESSED_PUBLIC_KEY_LENGTH + ? publicKey.slice(2) + : publicKey; + return `enode://${nodeId}@${host}:${port}?discport=${discoveryPort}`; }; const sampleValidator = sampleNode(SAMPLE_VALIDATOR_INDEX); From 549c8784aebabb97e9b3101b73cab7ccd7e94775 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:24:07 +0200 Subject: [PATCH 05/17] feat(charts): update Helm chart values for network nodes - Enhanced `values.yaml` to include default Prometheus annotations for pod metrics scraping. - Updated liveness and readiness probe configurations to use specific paths and ports for the Besu application. - Ensured consistency in pod annotations and labels for Kubernetes deployments. --- .../templates/statefulset-rpc.yaml | 27 ++++++------------- .../templates/statefulset-validator.yaml | 9 ++++++- .../network/charts/network-nodes/values.yaml | 24 +++++++++++++---- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml index 71825e3..568187c 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml @@ -27,6 +27,7 @@ spec: {{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }} {{- $shouldMountPersistence := $persistenceEnabled }} {{- $rpcReplicaBudget := .Values.rpcReplicaCount | int }} + podManagementPolicy: Parallel replicas: {{ .Values.rpcReplicaCount }} serviceName: {{ include "nodes.fullname" . }} {{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }} @@ -69,6 +70,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -78,6 +83,7 @@ spec: command: - /bin/sh - -c + # Advertise the pod IP so peers see a reachable address. args: - | exec @@ -85,7 +91,8 @@ spec: --config-file=/etc/besu/config.toml \ --Xdns-enabled=true \ --Xdns-update-enabled=true \ - --min-gas-price={{ .Values.config.minGasPrice }} + --min-gas-price={{ .Values.config.minGasPrice }} \ + --p2p-host=${POD_IP} ports: - name: json-rpc containerPort: {{ .Values.service.ports.rpc }} @@ -131,10 +138,6 @@ spec: mountPath: /etc/besu/static-nodes.json subPath: static-nodes.json readOnly: true - - name: besu-private-key - mountPath: {{ printf "/data/%s" $privateKeyFilename }} - subPathExpr: "$(POD_NAME)/privateKey" - readOnly: true {{- if $shouldMountPersistence }} - name: {{ $volumeName }} mountPath: /data @@ -159,20 +162,6 @@ spec: - name: besu-static-nodes configMap: name: besu-static-nodes - - name: besu-private-key - projected: - {{- if gt $rpcReplicaBudget 0 }} - sources: - {{- range $i, $_ := until $rpcReplicaBudget }} - - secret: - name: {{ printf "besu-node-rpc-node-%d-private-key" (add $i 1) }} - items: - - key: privateKey - path: {{ printf "%s-rpc-%d/privateKey" (include "nodes.fullname" $root) $i }} - {{- end }} - {{- else }} - sources: [] - {{- end }} {{- if $useExistingClaim }} - name: {{ $volumeName }} persistentVolumeClaim: diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml index 1622f71..d1f21b4 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -27,6 +27,7 @@ spec: {{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }} {{- $shouldMountPersistence := $persistenceEnabled }} {{- $validatorReplicaBudget := .Values.validatorReplicaCount | int }} + podManagementPolicy: Parallel replicas: {{ .Values.validatorReplicaCount }} serviceName: {{ include "nodes.fullname" . }} {{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }} @@ -69,6 +70,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -78,6 +83,7 @@ spec: command: - /bin/sh - -c + # Advertise the pod IP so peers see a reachable address. args: - | exec @@ -85,7 +91,8 @@ spec: --config-file=/etc/besu/config.toml \ --Xdns-enabled=true \ --Xdns-update-enabled=true \ - --min-gas-price={{ .Values.config.minGasPrice }} + --min-gas-price={{ .Values.config.minGasPrice }} \ + --p2p-host=${POD_IP} ports: - name: json-rpc containerPort: {{ .Values.service.ports.rpc }} diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index 17951ec..d8fa511 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -35,7 +35,12 @@ serviceAccount: # This is for setting Kubernetes Annotations to a Pod. # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ -podAnnotations: {} +# Defaults ensure Prometheus can scrape Besu's metrics endpoint without extra overrides. +podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/path: /metrics + prometheus.io/port: "9545" + prometheus.io/scheme: http # This is for setting Kubernetes Labels to a Pod. # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ podLabels: {} @@ -250,14 +255,23 @@ resources: # memory: 128Mi # This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +# Besu publishes dedicated /liveness and /readiness endpoints on the JSON-RPC service port; probe timings cover startup warm-up. livenessProbe: httpGet: - path: / - port: http + path: /liveness + port: json-rpc + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 2 + failureThreshold: 3 readinessProbe: httpGet: - path: / - port: http + path: /readiness?minPeers=0&maxBlocksBehind=100 + port: json-rpc + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 2 + failureThreshold: 3 # Persistent storage configuration for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ persistence: From 44d30bdf6188e67811ce7588132d0d83d2cb1e44 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:24:27 +0200 Subject: [PATCH 06/17] feat(charts): update Helm chart values for network nodes - Modified `README.md` to reflect changes in liveness and readiness probe configurations, including updated paths and ports for the Besu application. - Enhanced pod annotations for Prometheus metrics scraping. - Ensured consistency in Helm chart values for network nodes, including ingress and persistence settings. --- charts/network/charts/network-nodes/README.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/charts/network/charts/network-nodes/README.md b/charts/network/charts/network-nodes/README.md index 18500a7..2a3b205 100644 --- a/charts/network/charts/network-nodes/README.md +++ b/charts/network/charts/network-nodes/README.md @@ -102,8 +102,12 @@ A Helm chart for Kubernetes | ingress.hosts[0].paths[0].path | string | `"/"` | | | ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | | ingress.tls | list | `[]` | | -| livenessProbe.httpGet.path | string | `"/"` | | -| livenessProbe.httpGet.port | string | `"http"` | | +| livenessProbe.failureThreshold | int | `3` | | +| livenessProbe.httpGet.path | string | `"/liveness"` | | +| livenessProbe.httpGet.port | string | `"json-rpc"` | | +| livenessProbe.initialDelaySeconds | int | `30` | | +| livenessProbe.periodSeconds | int | `10` | | +| livenessProbe.timeoutSeconds | int | `2` | | | nameOverride | string | `""` | | | nodeSelector | object | `{}` | | | openShiftRoute | object | `{"alternateBackends":[],"annotations":{},"enabled":false,"host":"","path":"","port":{"targetPort":"http"},"tls":null,"to":{"weight":100},"wildcardPolicy":""}` | Expose the service via OpenShift Route when running on OpenShift clusters This relies on the OpenShift router to make the network nodes reachable externally. | @@ -121,11 +125,18 @@ A Helm chart for Kubernetes | persistence.subPath | string | `""` | | | persistence.volumeMode | string | `""` | | | persistence.volumeName | string | `"data"` | | -| podAnnotations | object | `{}` | | +| podAnnotations."prometheus.io/path" | string | `"/metrics"` | | +| podAnnotations."prometheus.io/port" | string | `"9545"` | | +| podAnnotations."prometheus.io/scheme" | string | `"http"` | | +| podAnnotations."prometheus.io/scrape" | string | `"true"` | | | podLabels | object | `{}` | | | podSecurityContext | object | `{}` | | -| readinessProbe.httpGet.path | string | `"/"` | | -| readinessProbe.httpGet.port | string | `"http"` | | +| readinessProbe.failureThreshold | int | `3` | | +| readinessProbe.httpGet.path | string | `"/readiness?minPeers=0&maxBlocksBehind=100"` | | +| readinessProbe.httpGet.port | string | `"json-rpc"` | | +| readinessProbe.initialDelaySeconds | int | `15` | | +| readinessProbe.periodSeconds | int | `10` | | +| readinessProbe.timeoutSeconds | int | `2` | | | resources | object | `{}` | | | rpcReplicaCount | int | `2` | | | securityContext | object | `{}` | | From 05a9eba3116f937a48c48aac05b3f736912f04b5 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:36:12 +0200 Subject: [PATCH 07/17] fix(charts): correct validator pod secret naming convention - Updated `values.yaml` to change the validator pod secret naming from 1-indexed to 0-indexed. - Adjusted related references in `build-command.ts` and `output.test.ts` to align with the new naming convention for validator secrets. - Ensured consistency across the Helm chart for network nodes regarding secret management. --- .../templates/statefulset-validator.yaml | 2 +- .../network/charts/network-nodes/values.yaml | 4 +-- src/cli/build-command.test.ts | 5 +-- src/cli/build-command.ts | 6 ++-- src/cli/output.test.ts | 31 ++++++++++--------- src/cli/output.ts | 4 ++- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml index d1f21b4..ad09855 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -172,7 +172,7 @@ spec: sources: {{- range $i, $_ := until $validatorReplicaBudget }} - secret: - name: {{ printf "besu-node-validator-%d-private-key" (add $i 1) }} + name: {{ printf "besu-node-validator-%d-private-key" $i }} items: - key: privateKey path: {{ printf "%s-validator-%d/privateKey" (include "nodes.fullname" $root) $i }} diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index d8fa511..6b7ca1b 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -5,7 +5,7 @@ # This sets the replica count for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ rpcReplicaCount: 2 # RPC pods expect Secrets named `besu-node-rpc-node--private-key` (1-indexed) to exist for private key mounts. -# Validator pods expect Secrets named `besu-node-validator--private-key` (1-indexed). +# Validator pods expect Secrets named `besu-node-validator--private-key` (0-indexed). validatorReplicaCount: 4 # This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ @@ -19,7 +19,7 @@ image: imagePullSecrets: [] # This is to override the chart name. nameOverride: "" -fullnameOverride: "" +fullnameOverride: "besu-node" # This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ serviceAccount: diff --git a/src/cli/build-command.test.ts b/src/cli/build-command.test.ts index 9155499..0c232a6 100644 --- a/src/cli/build-command.test.ts +++ b/src/cli/build-command.test.ts @@ -71,8 +71,9 @@ const expectedStaticNodeUri = ( namespace === undefined || namespace.trim().length === 0 ? undefined : namespace.trim(); - const podName = `besu-node-validator-${index}-0`; - const serviceName = `besu-node-validator-${index}`; + const ordinal = index - 1; + const podName = `besu-node-validator-${ordinal}`; + const serviceName = "besu-node-validator"; const segments = [podName, serviceName]; if (normalizedNamespace) { segments.push(normalizedNamespace); diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 0d29c70..449f492 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -153,8 +153,10 @@ const createStaticNodeEntries = ( const normalizedNamespace = normalizeStaticNodeNamespace(namespace); return nodes.map((node) => { - const podName = `besu-node-validator-${node.index}-0`; - const serviceName = `besu-node-validator-${node.index}`; + // StatefulSet pod ordinals start at 0 even though our generator indexes start at 1. + const ordinal = node.index - 1; + const podName = `besu-node-validator-${ordinal}`; + const serviceName = "besu-node-validator"; const segments = [podName, serviceName]; if (normalizedNamespace) { segments.push(normalizedNamespace); diff --git a/src/cli/output.test.ts b/src/cli/output.test.ts index d9f9449..8fe8bca 100644 --- a/src/cli/output.test.ts +++ b/src/cli/output.test.ts @@ -121,8 +121,9 @@ const staticNodeUri = ( namespace === undefined || namespace.trim().length === 0 ? undefined : namespace.trim(); - const podName = `besu-node-validator-${node.index}-0`; - const serviceName = `besu-node-validator-${node.index}`; + const ordinal = node.index - 1; + const podName = `besu-node-validator-${ordinal}`; + const serviceName = "besu-node-validator"; const segments = [podName, serviceName]; if (trimmedNamespace) { segments.push(trimmedNamespace); @@ -188,10 +189,10 @@ describe("outputResult", () => { "besu-faucet-enode", "besu-faucet-private-key", "besu-faucet-pubkey", - "besu-node-validator-1-address", - "besu-node-validator-1-enode", - "besu-node-validator-1-private-key", - "besu-node-validator-1-pubkey", + "besu-node-validator-0-address", + "besu-node-validator-0-enode", + "besu-node-validator-0-private-key", + "besu-node-validator-0-pubkey", "genesis", "static-nodes.json", ].sort() @@ -290,7 +291,7 @@ describe("outputResult", () => { expect(createdConfigMaps).toHaveLength(EXPECTED_CONFIGMAP_COUNT); expect(createdSecrets).toHaveLength(EXPECTED_SECRET_COUNT); const mapNames = createdConfigMaps.map((entry) => entry.name).sort(); - expect(mapNames).toContain("besu-node-validator-1-address"); + expect(mapNames).toContain("besu-node-validator-0-address"); expect(mapNames).toContain("besu-genesis"); expect(mapNames).toContain("besu-faucet-address"); expect(mapNames).toContain("besu-faucet-pubkey"); @@ -299,10 +300,10 @@ describe("outputResult", () => { const secretNames = createdSecrets.map((entry) => entry.name).sort(); expect(secretNames).toEqual([ "besu-faucet-private-key", - "besu-node-validator-1-private-key", + "besu-node-validator-0-private-key", ]); const privateKeySecret = createdSecrets.find((entry) => - entry.name.endsWith("validator-1-private-key") + entry.name.endsWith("validator-0-private-key") ); expect(privateKeySecret?.data?.privateKey).toMatch(HEX_PREFIX_PATTERN); const staticNodesConfig = createdConfigMaps.find( @@ -356,7 +357,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "ConfigMap besu-node-validator-1-address already exists. Delete it or choose a different output target." + "ConfigMap besu-node-validator-0-address already exists. Delete it or choose a different output target." ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -402,7 +403,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Secret besu-node-validator-1-private-key already exists. Delete it or choose a different output target." + "Secret besu-node-validator-0-private-key already exists. Delete it or choose a different output target." ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -545,7 +546,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-1-address: boom" + "Failed to create ConfigMap besu-node-validator-0-address: boom" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -584,7 +585,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-1-address: failed" + "Failed to create ConfigMap besu-node-validator-0-address: failed" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -625,7 +626,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-1-address: unknown error" + "Failed to create ConfigMap besu-node-validator-0-address: unknown error" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -666,7 +667,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-1-address: denied" + "Failed to create ConfigMap besu-node-validator-0-address: denied" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; diff --git a/src/cli/output.ts b/src/cli/output.ts index 99f6c35..f47aa9c 100644 --- a/src/cli/output.ts +++ b/src/cli/output.ts @@ -177,7 +177,9 @@ const outputToKubernetes = async (payload: OutputPayload): Promise => { const createValidatorSpecs = (nodes: readonly IndexedNode[]): ConfigMapSpec[] => nodes.flatMap((node) => { - const base = `besu-node-validator-${node.index}`; + // Align artifact names with 0-indexed StatefulSet pod ordinals. + const ordinal = node.index - 1; + const base = `besu-node-validator-${ordinal}`; return [ { name: `${base}-address`, key: "address", value: node.address }, { From 37b36ad1abb7d21610adef59600abce428fdf7c7 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:36:21 +0200 Subject: [PATCH 08/17] fix(charts): update network nodes README for configuration values - Corrected the formatting of configuration values in `README.md` for network nodes, ensuring clarity and consistency. - Adjusted the `fullnameOverride` value to reflect the correct naming convention. - Ensured that all configuration options are accurately documented for user reference. --- charts/network/charts/network-nodes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/network/charts/network-nodes/README.md b/charts/network/charts/network-nodes/README.md index 2a3b205..eca4967 100644 --- a/charts/network/charts/network-nodes/README.md +++ b/charts/network/charts/network-nodes/README.md @@ -89,7 +89,7 @@ A Helm chart for Kubernetes | config.ws.host | string | `"0.0.0.0"` | | | config.ws.maxActiveConnections | int | `2000` | | | config.ws.maxFrameSize | int | `2097152` | | -| fullnameOverride | string | `""` | | +| fullnameOverride | string | `"besu-node"` | | | httpRoute | object | `{"annotations":{},"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"gateway","sectionName":"http"}],"rules":[{"matches":[{"path":{"type":"PathPrefix","value":"/headers"}}]}]}` | Expose the service via gateway-api HTTPRoute Requires Gateway API resources and suitable controller installed within the cluster (see: https://gateway-api.sigs.k8s.io/guides/) | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"docker.io/hyperledger/besu"` | | From 60b6f320eb91654b7bf122aa1451076d9c017266 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:39:55 +0200 Subject: [PATCH 09/17] chore(charts): update Chart.lock for network dependencies - Updated the Chart.lock file to reflect the latest dependencies for the network-bootstrapper and network-nodes. - Ensured that the lock file is in sync with the current dependency versions and configurations. --- charts/network/Chart.lock | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 charts/network/Chart.lock diff --git a/charts/network/Chart.lock b/charts/network/Chart.lock deleted file mode 100644 index ae917ed..0000000 --- a/charts/network/Chart.lock +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: -- name: network-bootstrapper - repository: "" - version: '*' -- name: network-nodes - repository: "" - version: '*' -digest: sha256:858da5a469d81d0ce21b0661002958546cb645d7cd1851dd85404930905e5094 -generated: "2025-09-17T14:38:28.190498+02:00" From db1b4d647db1bd27f44760f4542ceeeb23551617 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:52:24 +0200 Subject: [PATCH 10/17] chore(charts): update Chart.lock and fix service configurations - Updated the Chart.lock file to ensure it reflects the latest dependencies for the network-bootstrapper and network-nodes. - Adjusted service configurations in `values.yaml` to correct the `fullnameOverride` for the bootstrapper and ensure consistency in service types across templates. - Made minor formatting adjustments in service templates to align with the updated configurations. --- .gitignore | 1 + .../charts/network-bootstrapper/values.yaml | 2 +- .../network-nodes/templates/service-p2p.yaml | 17 --------- .../network-nodes/templates/service-rpc.yaml | 37 +++++++++++++++++++ .../network-nodes/templates/service.yaml | 8 +++- .../templates/statefulset-rpc.yaml | 5 ++- .../templates/statefulset-validator.yaml | 3 ++ .../network/charts/network-nodes/values.yaml | 1 - src/cli/build-command.test.ts | 2 +- src/cli/build-command.ts | 2 +- src/cli/output.test.ts | 2 +- 11 files changed, 55 insertions(+), 25 deletions(-) delete mode 100644 charts/network/charts/network-nodes/templates/service-p2p.yaml create mode 100644 charts/network/charts/network-nodes/templates/service-rpc.yaml diff --git a/.gitignore b/.gitignore index a14702c..385a9fc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store +Chart.lock \ No newline at end of file diff --git a/charts/network/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml index 78f1342..17b4aa6 100644 --- a/charts/network/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -12,7 +12,7 @@ imagePullSecrets: [] # -- (string) Short name override applied to chart-scoped resource names. nameOverride: "" # -- (string) Fully qualified name override for resources created by this release. -fullnameOverride: "" +fullnameOverride: "bootstrapper" # -- (object) Service account configuration for the bootstrapper pod. See https://kubernetes.io/docs/concepts/security/service-accounts/ for details. serviceAccount: diff --git a/charts/network/charts/network-nodes/templates/service-p2p.yaml b/charts/network/charts/network-nodes/templates/service-p2p.yaml deleted file mode 100644 index fc66483..0000000 --- a/charts/network/charts/network-nodes/templates/service-p2p.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "nodes.fullname" . }}-p2p - labels: {{- include "nodes.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.p2pType }} - ports: - - name: rlpx - port: {{ .Values.service.ports.rlpx }} - targetPort: rlpx - protocol: TCP - - name: discovery - port: {{ .Values.service.ports.discovery }} - targetPort: discovery - protocol: UDP - selector: {{- include "nodes.selectorLabels" . | nindent 4 }} diff --git a/charts/network/charts/network-nodes/templates/service-rpc.yaml b/charts/network/charts/network-nodes/templates/service-rpc.yaml new file mode 100644 index 0000000..7f1c145 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/service-rpc.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nodes.fullname" . }}-rpc + labels: + {{- include "nodes.labels" . | nindent 4 }} + app.kubernetes.io/component: rpc +spec: + type: {{ .Values.service.type }} + ports: + - name: json-rpc + port: {{ .Values.service.ports.rpc }} + targetPort: json-rpc + protocol: TCP + - name: ws + port: {{ .Values.service.ports.ws }} + targetPort: ws + protocol: TCP + - name: graphql + port: {{ .Values.service.ports.graphql }} + targetPort: graphql + protocol: TCP + - name: rlpx + port: {{ .Values.service.ports.rlpx }} + targetPort: rlpx + protocol: TCP + - name: discovery + port: {{ .Values.service.ports.discovery }} + targetPort: discovery + protocol: UDP + - name: metrics + port: {{ .Values.service.ports.metrics }} + targetPort: metrics + protocol: TCP + selector: + {{- include "nodes.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: rpc diff --git a/charts/network/charts/network-nodes/templates/service.yaml b/charts/network/charts/network-nodes/templates/service.yaml index dc8f1f0..20e1a74 100644 --- a/charts/network/charts/network-nodes/templates/service.yaml +++ b/charts/network/charts/network-nodes/templates/service.yaml @@ -2,7 +2,9 @@ apiVersion: v1 kind: Service metadata: name: {{ include "nodes.fullname" . }} - labels: {{- include "nodes.labels" . | nindent 4 }} + labels: + {{- include "nodes.labels" . | nindent 4 }} + app.kubernetes.io/component: validator spec: type: {{ .Values.service.type }} ports: @@ -30,4 +32,6 @@ spec: port: {{ .Values.service.ports.metrics }} targetPort: metrics protocol: TCP - selector: {{- include "nodes.selectorLabels" . | nindent 4 }} + selector: + {{- include "nodes.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: validator diff --git a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml index 568187c..287cd84 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-rpc.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "nodes.fullname" . }}-rpc labels: {{- include "nodes.labels" . | nindent 4 }} + app.kubernetes.io/component: rpc spec: {{- $root := . }} {{- $persistence := .Values.persistence | default (dict) }} @@ -29,7 +30,7 @@ spec: {{- $rpcReplicaBudget := .Values.rpcReplicaCount | int }} podManagementPolicy: Parallel replicas: {{ .Values.rpcReplicaCount }} - serviceName: {{ include "nodes.fullname" . }} + serviceName: {{ include "nodes.fullname" . }}-rpc {{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }} persistentVolumeClaimRetentionPolicy: {{- if ne $whenDeleted "" }} @@ -42,6 +43,7 @@ spec: selector: matchLabels: {{- include "nodes.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: rpc template: metadata: {{- with .Values.podAnnotations }} @@ -50,6 +52,7 @@ spec: {{- end }} labels: {{- include "nodes.labels" . | nindent 8 }} + app.kubernetes.io/component: rpc {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml index ad09855..e2d8e00 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "nodes.fullname" . }}-validator labels: {{- include "nodes.labels" . | nindent 4 }} + app.kubernetes.io/component: validator spec: {{- $root := . }} {{- $persistence := .Values.persistence | default (dict) }} @@ -42,6 +43,7 @@ spec: selector: matchLabels: {{- include "nodes.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: validator template: metadata: {{- with .Values.podAnnotations }} @@ -50,6 +52,7 @@ spec: {{- end }} labels: {{- include "nodes.labels" . | nindent 8 }} + app.kubernetes.io/component: validator {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index 6b7ca1b..4886ad2 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -62,7 +62,6 @@ securityContext: service: # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types type: ClusterIP - p2pType: NodePort # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports ports: rpc: 8545 diff --git a/src/cli/build-command.test.ts b/src/cli/build-command.test.ts index 0c232a6..149dffb 100644 --- a/src/cli/build-command.test.ts +++ b/src/cli/build-command.test.ts @@ -73,7 +73,7 @@ const expectedStaticNodeUri = ( : namespace.trim(); const ordinal = index - 1; const podName = `besu-node-validator-${ordinal}`; - const serviceName = "besu-node-validator"; + const serviceName = "besu-node"; const segments = [podName, serviceName]; if (normalizedNamespace) { segments.push(normalizedNamespace); diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 449f492..72663ec 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -156,7 +156,7 @@ const createStaticNodeEntries = ( // StatefulSet pod ordinals start at 0 even though our generator indexes start at 1. const ordinal = node.index - 1; const podName = `besu-node-validator-${ordinal}`; - const serviceName = "besu-node-validator"; + const serviceName = "besu-node"; // Headless service fronting validator pods const segments = [podName, serviceName]; if (normalizedNamespace) { segments.push(normalizedNamespace); diff --git a/src/cli/output.test.ts b/src/cli/output.test.ts index 8fe8bca..f8d58e4 100644 --- a/src/cli/output.test.ts +++ b/src/cli/output.test.ts @@ -123,7 +123,7 @@ const staticNodeUri = ( : namespace.trim(); const ordinal = node.index - 1; const podName = `besu-node-validator-${ordinal}`; - const serviceName = "besu-node-validator"; + const serviceName = "besu-node"; const segments = [podName, serviceName]; if (trimmedNamespace) { segments.push(trimmedNamespace); From 6a05361c3dc8d9aaedb6a8c8c1c513162415ebed Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 16:52:31 +0200 Subject: [PATCH 11/17] fix(charts): update README for network-bootstrapper and network-nodes configuration values - Corrected formatting and descriptions of configuration values in `README.md` for both network-bootstrapper and network-nodes. - Ensured clarity and consistency in the documentation of parameters such as `clusterDomain`, `defaultStaticNodeDiscoveryPort`, and `fullnameOverride`. - Updated image settings to reflect accurate container image configurations for the network bootstrapper workload. --- charts/network/charts/network-bootstrapper/README.md | 2 +- charts/network/charts/network-nodes/README.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/charts/network/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md index c0573fe..35f5695 100644 --- a/charts/network/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -18,7 +18,7 @@ A Helm chart for Kubernetes | clusterDomain | string | `"svc.cluster.local"` | Kubernetes cluster DNS suffix. Used when staticNodeDomain is not explicitly provided. | | defaultStaticNodeDiscoveryPort | int | `30303` | Default discovery port for generated static node URIs when per-run settings are omitted. | | defaultStaticNodePort | int | `30303` | Default P2P port for generated static node URIs when per-run settings are omitted. | -| fullnameOverride | string | `""` | Fully qualified name override for resources created by this release. | +| fullnameOverride | string | `"bootstrapper"` | Fully qualified name override for resources created by this release. | | image | object | `{"pullPolicy":"IfNotPresent","repository":"ghcr.io/settlemint/network-bootstrapper","tag":""}` | Container image settings for the network bootstrapper workload. See https://kubernetes.io/docs/concepts/containers/images/ for background. | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy controlling when Kubernetes re-fetches the image layer manifest. | | image.repository | string | `"ghcr.io/settlemint/network-bootstrapper"` | OCI repository hosting the network bootstrapper image. | diff --git a/charts/network/charts/network-nodes/README.md b/charts/network/charts/network-nodes/README.md index eca4967..975efd9 100644 --- a/charts/network/charts/network-nodes/README.md +++ b/charts/network/charts/network-nodes/README.md @@ -140,7 +140,6 @@ A Helm chart for Kubernetes | resources | object | `{}` | | | rpcReplicaCount | int | `2` | | | securityContext | object | `{}` | | -| service.p2pType | string | `"NodePort"` | | | service.ports.discovery | int | `30303` | | | service.ports.graphql | int | `8547` | | | service.ports.metrics | int | `9545` | | From 21a94c4d581947d6008133d81e1cbc0257c21030 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 17:06:12 +0200 Subject: [PATCH 12/17] fix(charts): update values.yaml for network-bootstrapper and network-nodes - Revised comments and descriptions in `values.yaml` for the network-bootstrapper to enhance clarity and consistency. - Updated the structure of configuration values to align with best practices, ensuring accurate documentation for users. - Made minor adjustments to the `statefulset-validator.yaml` template to address YAML parsing errors and improve overall configuration integrity. --- .../charts/network-bootstrapper/values.yaml | 92 +++++++++---------- .../templates/statefulset-validator.yaml | 1 - .../network/charts/network-nodes/values.yaml | 3 - 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/charts/network/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml index 17b4aa6..af372d8 100644 --- a/charts/network/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -1,47 +1,49 @@ -# -- Container image settings for the network bootstrapper workload. See https://kubernetes.io/docs/concepts/containers/images/ for background. +# image configures the container image for the network bootstrapper job. image: - # -- (string) OCI repository hosting the network bootstrapper image. + # image.repository is the OCI registry path that hosts the bootstrapper image. repository: ghcr.io/settlemint/network-bootstrapper - # -- (string) Image pull policy controlling when Kubernetes re-fetches the image layer manifest. + # image.pullPolicy controls when Kubernetes re-pulls the bootstrapper image layers. pullPolicy: IfNotPresent - # -- (string) Image tag override. Defaults to the chart's `.appVersion` when left empty. + # image.tag overrides the image tag; leave empty to fall back to the chart appVersion. tag: "" -# -- (list) Image pull secrets enabling access to private registries. See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ for usage. +# imagePullSecrets lists Kubernetes secrets used to authenticate against private registries. imagePullSecrets: [] -# -- (string) Short name override applied to chart-scoped resource names. + +# nameOverride replaces the short chart name used in resource naming helpers. nameOverride: "" -# -- (string) Fully qualified name override for resources created by this release. + +# fullnameOverride replaces the fully qualified resource name generated by the helpers template. fullnameOverride: "bootstrapper" -# -- (object) Service account configuration for the bootstrapper pod. See https://kubernetes.io/docs/concepts/security/service-accounts/ for details. +# serviceAccount encapsulates the ServiceAccount resource bound to the bootstrapper pod. serviceAccount: - # -- (bool) Whether to create a service account automatically. + # serviceAccount.create toggles creation of a dedicated ServiceAccount for this release. create: true - # -- (bool) Automatically mount the service account token into the pod. + # serviceAccount.automount determines whether the pod receives a projected API token. automount: true - # -- (object) Additional metadata annotations applied to the service account object. + # serviceAccount.annotations adds metadata annotations to the generated (or referenced) ServiceAccount. annotations: {} - # -- (string) Existing service account name to use instead of one generated by the chart. - # If unset and `serviceAccount.create` is true, a name is derived from the chart fullname. + # serviceAccount.name supplies an existing ServiceAccount to reuse instead of creating one automatically. name: "" -# -- (object) RBAC resources granting ConfigMap access for Kubernetes output workflows. +# rbac configures Role and RoleBinding manifests required for Kubernetes output mode. rbac: - # -- (bool) Whether to create Role and RoleBinding objects targeting the service account. + # rbac.create controls whether RBAC resources are rendered for the bootstrapper ServiceAccount. create: true -# -- (object) Pod-level annotations merged onto the generated pod template metadata. See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/. +# podAnnotations adds custom annotations to the bootstrapper pod template metadata. podAnnotations: {} -# -- (object) Pod-level labels applied to the pod template metadata. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. + +# podLabels adds custom labels to the bootstrapper pod template metadata. podLabels: {} -# -- (object) Pod-level security context applied to all containers in the pod. +# podSecurityContext defines default security context attributes applied at the pod level. podSecurityContext: {} # fsGroup: 2000 -# -- (object) Container security context applied to the bootstrapper container. +# securityContext defines the container security context for the bootstrapper container. securityContext: {} # capabilities: @@ -51,13 +53,9 @@ securityContext: # runAsNonRoot: true # runAsUser: 1000 -# -- (object) Resource requests and limits for the bootstrapper container. +# resources specifies CPU and memory requests or limits for the bootstrapper container. resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi @@ -65,64 +63,64 @@ resources: # cpu: 100m # memory: 128Mi -# -- (list) Additional volumes injected into the deployment pod spec. +# volumes lists extra volumes attached to the bootstrapper pod for custom inputs or outputs. volumes: [] # - name: foo # secret: # secretName: mysecret # optional: false -# -- (list) Additional volume mounts added to the bootstrapper container. +# volumeMounts lists additional volume mounts injected into the bootstrapper container definition. volumeMounts: [] # - name: foo # mountPath: "/etc/foo" # readOnly: true -# -- (object) Node selector constraints for scheduling the bootstrapper pod. +# nodeSelector constrains scheduling of the bootstrapper pod to nodes with matching labels. nodeSelector: {} -# -- (list) Kubernetes tolerations assigned to the bootstrapper pod. +# tolerations allows the bootstrapper pod to schedule onto tainted nodes. tolerations: [] -# -- (object) Affinity and anti-affinity rules influencing pod placement. +# affinity defines node or pod affinity rules that steer bootstrapper pod placement. affinity: {} -# -- (string) Kubernetes cluster DNS suffix. Used when staticNodeDomain is not explicitly provided. +# clusterDomain is the Kubernetes DNS suffix appended when generating static node hostnames. clusterDomain: svc.cluster.local -# -- (int) Default P2P port for generated static node URIs when per-run settings are omitted. +# defaultStaticNodePort is the Besu P2P TCP port used in generated enode URIs when not overridden. defaultStaticNodePort: 30303 -# -- (int) Default discovery port for generated static node URIs when per-run settings are omitted. +# defaultStaticNodeDiscoveryPort is the Besu UDP discovery port used in generated enode URIs when not overridden. defaultStaticNodeDiscoveryPort: 30303 -# -- (object) Network bootstrapper CLI settings translated into command-line flags. +# settings maps to CLI flags for the bootstrapper command executed by the job. settings: - # -- (int) Number of validator node definitions the bootstrapper generates. Default: 4. + # settings.validators controls how many validator identities the bootstrapper produces (default 4). validators: - # -- (string) DNS suffix appended to static node hostnames (for example: "svc.cluster.local"). Default: `clusterDomain`. + # settings.staticNodeDomain overrides the DNS suffix appended to generated static node hostnames. staticNodeDomain: - # -- (string) Namespace segment inserted between service and domain in static node hostnames. Default: release namespace. + # settings.staticNodeNamespace overrides the namespace segment embedded into static node hostnames. staticNodeNamespace: - # -- (int) P2P port used in static node enode URIs. Default: `defaultStaticNodePort`. + # settings.staticNodePort overrides the TCP P2P port encoded in static node enode URIs. staticNodePort: - # -- (int) Discovery port used in static node enode URIs. Default: `defaultStaticNodeDiscoveryPort`. + # settings.staticNodeDiscoveryPort overrides the UDP discovery port encoded in static node enode URIs. staticNodeDiscoveryPort: - # -- (string) Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. + # settings.allocations provides a path to a JSON allocations file that seeds pre-funded accounts. allocations: - # -- (string) Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". + # settings.outputType selects where generated artefacts are written: screen, file, or kubernetes. outputType: kubernetes - # -- (string) Consensus engine to configure for the network (IBFTv2 or QBFT). Default: "QBFT". + # settings.consensus selects the Besu consensus engine (QBFT or IBFTv2). consensus: - # -- (int) Explicit chain ID applied to the genesis configuration. Defaults to a random value in the 40000-50000 range when omitted. + # settings.chainId overrides the randomly generated chain ID used in the genesis file. chainId: - # -- (int) Target block time in seconds encoded into genesis. Default: 2. + # settings.secondsPerBlock sets the target block interval encoded in genesis. secondsPerBlock: - # -- (int) Genesis block gas limit value expressed in decimal. Default: 9007199254740991. + # settings.gasLimit sets the genesis block gas limit in decimal format. gasLimit: - # -- (int) Base gas price in wei applied to the chain. Default: 0. + # settings.gasPrice sets the base gas price in wei recorded in genesis metadata. gasPrice: - # -- (int) Maximum EVM stack size allowed for contract execution. Default: 2048. + # settings.evmStackSize sets the maximum EVM stack size available to smart contracts. evmStackSize: - # -- (int) Contract size limit in bytes enforced by the EVM. Default: 2147483647. + # settings.contractSizeLimit sets the maximum contract bytecode size permitted by the EVM. contractSizeLimit: diff --git a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml index e2d8e00..a7b371a 100644 --- a/charts/network/charts/network-nodes/templates/statefulset-validator.yaml +++ b/charts/network/charts/network-nodes/templates/statefulset-validator.yaml @@ -28,7 +28,6 @@ spec: {{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }} {{- $shouldMountPersistence := $persistenceEnabled }} {{- $validatorReplicaBudget := .Values.validatorReplicaCount | int }} - podManagementPolicy: Parallel replicas: {{ .Values.validatorReplicaCount }} serviceName: {{ include "nodes.fullname" . }} {{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }} diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index 4886ad2..f0d6bab 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -1,6 +1,3 @@ -# Default values for nodes. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. # This sets the replica count for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ rpcReplicaCount: 2 From 6a24d203af5e68157d111753e488ab9ac4e9a354 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 17:06:23 +0200 Subject: [PATCH 13/17] fix(charts): update README and values.yaml for network-bootstrapper and network-nodes - Revised the README.md to improve clarity and consistency in the documentation of configuration values for both network-bootstrapper and network-nodes. - Updated the structure of configuration values in values.yaml to align with best practices, ensuring accurate documentation for users. - Made minor adjustments to enhance the overall integrity and readability of the configuration files. --- .../charts/network-bootstrapper/README.md | 80 +++--- .../network/charts/network-nodes/values.yaml | 236 ++++++++++++------ 2 files changed, 203 insertions(+), 113 deletions(-) diff --git a/charts/network/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md index 35f5695..274d346 100644 --- a/charts/network/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -14,45 +14,41 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | Affinity and anti-affinity rules influencing pod placement. | -| clusterDomain | string | `"svc.cluster.local"` | Kubernetes cluster DNS suffix. Used when staticNodeDomain is not explicitly provided. | -| defaultStaticNodeDiscoveryPort | int | `30303` | Default discovery port for generated static node URIs when per-run settings are omitted. | -| defaultStaticNodePort | int | `30303` | Default P2P port for generated static node URIs when per-run settings are omitted. | -| fullnameOverride | string | `"bootstrapper"` | Fully qualified name override for resources created by this release. | -| image | object | `{"pullPolicy":"IfNotPresent","repository":"ghcr.io/settlemint/network-bootstrapper","tag":""}` | Container image settings for the network bootstrapper workload. See https://kubernetes.io/docs/concepts/containers/images/ for background. | -| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy controlling when Kubernetes re-fetches the image layer manifest. | -| image.repository | string | `"ghcr.io/settlemint/network-bootstrapper"` | OCI repository hosting the network bootstrapper image. | -| image.tag | string | `""` | Image tag override. Defaults to the chart's `.appVersion` when left empty. | -| imagePullSecrets | list | `[]` | Image pull secrets enabling access to private registries. See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ for usage. | -| nameOverride | string | `""` | Short name override applied to chart-scoped resource names. | -| nodeSelector | object | `{}` | Node selector constraints for scheduling the bootstrapper pod. | -| podAnnotations | object | `{}` | Pod-level annotations merged onto the generated pod template metadata. See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/. | -| podLabels | object | `{}` | Pod-level labels applied to the pod template metadata. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. | -| podSecurityContext | object | `{}` | Pod-level security context applied to all containers in the pod. | -| rbac | object | `{"create":true}` | RBAC resources granting ConfigMap access for Kubernetes output workflows. | -| rbac.create | bool | `true` | Whether to create Role and RoleBinding objects targeting the service account. | -| resources | object | `{}` | Resource requests and limits for the bootstrapper container. | -| securityContext | object | `{}` | Container security context applied to the bootstrapper container. | -| serviceAccount | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | Service account configuration for the bootstrapper pod. See https://kubernetes.io/docs/concepts/security/service-accounts/ for details. | -| serviceAccount.annotations | object | `{}` | Additional metadata annotations applied to the service account object. | -| serviceAccount.automount | bool | `true` | Automatically mount the service account token into the pod. | -| serviceAccount.create | bool | `true` | Whether to create a service account automatically. | -| serviceAccount.name | string | `""` | Existing service account name to use instead of one generated by the chart. If unset and `serviceAccount.create` is true, a name is derived from the chart fullname. | -| settings | object | `{"allocations":null,"chainId":null,"consensus":null,"contractSizeLimit":null,"evmStackSize":null,"gasLimit":null,"gasPrice":null,"outputType":"kubernetes","secondsPerBlock":null,"staticNodeDiscoveryPort":null,"staticNodeDomain":null,"staticNodeNamespace":null,"staticNodePort":null,"validators":null}` | Network bootstrapper CLI settings translated into command-line flags. | -| settings.allocations | string | `nil` | Filesystem path, accessible to the job, pointing to a JSON file with initial account allocations. Omit to skip pre-funded accounts. | -| settings.chainId | int | `nil` | Explicit chain ID applied to the genesis configuration. Defaults to a random value in the 40000-50000 range when omitted. | -| settings.consensus | string | `nil` | Consensus engine to configure for the network (IBFTv2 or QBFT). Default: "QBFT". | -| settings.contractSizeLimit | int | `nil` | Contract size limit in bytes enforced by the EVM. Default: 2147483647. | -| settings.evmStackSize | int | `nil` | Maximum EVM stack size allowed for contract execution. Default: 2048. | -| settings.gasLimit | int | `nil` | Genesis block gas limit value expressed in decimal. Default: 9007199254740991. | -| settings.gasPrice | int | `nil` | Base gas price in wei applied to the chain. Default: 0. | -| settings.outputType | string | `"kubernetes"` | Destination for generated artefacts: `screen` (stdout), `file` (write to volume), or `kubernetes` (persist as Kubernetes secrets/configmaps). Default: "screen". | -| settings.secondsPerBlock | int | `nil` | Target block time in seconds encoded into genesis. Default: 2. | -| settings.staticNodeDiscoveryPort | int | `nil` | Discovery port used in static node enode URIs. Default: `defaultStaticNodeDiscoveryPort`. | -| settings.staticNodeDomain | string | `nil` | DNS suffix appended to static node hostnames (for example: "svc.cluster.local"). Default: `clusterDomain`. | -| settings.staticNodeNamespace | string | `nil` | Namespace segment inserted between service and domain in static node hostnames. Default: release namespace. | -| settings.staticNodePort | int | `nil` | P2P port used in static node enode URIs. Default: `defaultStaticNodePort`. | -| settings.validators | int | `nil` | Number of validator node definitions the bootstrapper generates. Default: 4. | -| tolerations | list | `[]` | Kubernetes tolerations assigned to the bootstrapper pod. | -| volumeMounts | list | `[]` | Additional volume mounts added to the bootstrapper container. | -| volumes | list | `[]` | Additional volumes injected into the deployment pod spec. | +| affinity | object | `{}` | | +| clusterDomain | string | `"svc.cluster.local"` | | +| defaultStaticNodeDiscoveryPort | int | `30303` | | +| defaultStaticNodePort | int | `30303` | | +| fullnameOverride | string | `"bootstrapper"` | | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"ghcr.io/settlemint/network-bootstrapper"` | | +| image.tag | string | `""` | | +| imagePullSecrets | list | `[]` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podLabels | object | `{}` | | +| podSecurityContext | object | `{}` | | +| rbac.create | bool | `true` | | +| resources | object | `{}` | | +| securityContext | object | `{}` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.automount | bool | `true` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `""` | | +| settings.allocations | string | `nil` | | +| settings.chainId | string | `nil` | | +| settings.consensus | string | `nil` | | +| settings.contractSizeLimit | string | `nil` | | +| settings.evmStackSize | string | `nil` | | +| settings.gasLimit | string | `nil` | | +| settings.gasPrice | string | `nil` | | +| settings.outputType | string | `"kubernetes"` | | +| settings.secondsPerBlock | string | `nil` | | +| settings.staticNodeDiscoveryPort | string | `nil` | | +| settings.staticNodeDomain | string | `nil` | | +| settings.staticNodeNamespace | string | `nil` | | +| settings.staticNodePort | string | `nil` | | +| settings.validators | string | `nil` | | +| tolerations | list | `[]` | | +| volumeMounts | list | `[]` | | +| volumes | list | `[]` | | diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index f0d6bab..13a8ea2 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -1,51 +1,58 @@ - -# This sets the replica count for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +# rpcReplicaCount sets the number of RPC StatefulSet replicas that serve JSON-RPC traffic. rpcReplicaCount: 2 -# RPC pods expect Secrets named `besu-node-rpc-node--private-key` (1-indexed) to exist for private key mounts. -# Validator pods expect Secrets named `besu-node-validator--private-key` (0-indexed). + +# validatorReplicaCount sets the number of validator StatefulSet replicas responsible for consensus. validatorReplicaCount: 4 -# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +# image configures the Besu container image used for both RPC and validator pods. image: + # image.repository points at the OCI registry location containing the Besu image. repository: docker.io/hyperledger/besu - # This sets the pull policy for images. + # image.pullPolicy determines when Kubernetes attempts to re-pull the image layers. pullPolicy: IfNotPresent + # image.tag pins the Besu version; update to consume a different upstream release. tag: "25.8.0" -# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +# imagePullSecrets lists Docker registry credentials needed to pull the Besu image. imagePullSecrets: [] -# This is to override the chart name. + +# nameOverride replaces the short chart name used in resource name templates. nameOverride: "" + +# fullnameOverride replaces the fully qualified release name used for resource naming. fullnameOverride: "besu-node" -# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +# serviceAccount configures the Kubernetes ServiceAccount bound to pods created by this chart. serviceAccount: - # Specifies whether a service account should be created + # serviceAccount.create controls whether the chart should create a ServiceAccount automatically. create: true - # Automatically mount a ServiceAccount's API credentials? + # serviceAccount.automount controls whether pods receive a projected ServiceAccount token. automount: true - # Annotations to add to the service account + # serviceAccount.annotations attaches metadata annotations to the ServiceAccount object. annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # serviceAccount.name references an existing ServiceAccount when creation is disabled. name: "" -# This is for setting Kubernetes Annotations to a Pod. -# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ -# Defaults ensure Prometheus can scrape Besu's metrics endpoint without extra overrides. +# podAnnotations applies metadata annotations to every Besu pod (validators and RPC nodes). podAnnotations: + # podAnnotations.prometheus.io/scrape enables Prometheus autoscraping of Besu metrics. prometheus.io/scrape: "true" + # podAnnotations.prometheus.io/path identifies the HTTP path exposing metrics. prometheus.io/path: /metrics + # podAnnotations.prometheus.io/port sets the metrics port label consumed by the scraper. prometheus.io/port: "9545" + # podAnnotations.prometheus.io/scheme selects the HTTP scheme used to scrape metrics. prometheus.io/scheme: http -# This is for setting Kubernetes Labels to a Pod. -# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + +# podLabels applies extra Kubernetes labels to every Besu pod for selection or organisation. podLabels: {} +# podSecurityContext defines pod-level Linux security settings shared by all containers in a pod. podSecurityContext: {} # fsGroup: 2000 +# securityContext defines container-level security controls for the Besu process. securityContext: {} # capabilities: @@ -55,51 +62,89 @@ securityContext: # runAsNonRoot: true # runAsUser: 1000 -# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +# service defines the Kubernetes Service exposing Besu endpoints. service: - # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + # service.type selects the Service controller type (ClusterIP, NodePort, LoadBalancer, etc.). type: ClusterIP - # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + # service.ports maps friendly names to Service ports for Besu endpoints. ports: + # service.ports.rpc exposes the HTTP JSON-RPC interface. rpc: 8545 + # service.ports.ws exposes the WebSocket JSON-RPC interface. ws: 8546 + # service.ports.graphql exposes the GraphQL API. graphql: 8547 + # service.ports.rlpx exposes the peer-to-peer RLPx TCP port. rlpx: 30303 + # service.ports.discovery exposes the peer discovery UDP port. discovery: 30303 + # service.ports.metrics exposes the Prometheus metrics endpoint. metrics: 9545 +# config captures the default besu.conf values applied to both validator and RPC nodes. config: + # config.logging controls the global logging verbosity for Besu. logging: INFO + # config.dataStorageFormat selects the ledger storage backend (FOREST or BONSAI). dataStorageFormat: FOREST + # config.privateKeyFilename names the file containing the node's private key inside mounted secrets. privateKeyFilename: privateKey + # config.bonsaiLimitTrieLogsEnabled toggles Bonsai limit trie logging for debugging state transitions. bonsaiLimitTrieLogsEnabled: false + # config.cacheLastBlocks specifies how many recent blocks are cached for quick access. cacheLastBlocks: 1024 + # config.minGasPrice sets the minimum gas price accepted for transactions. minGasPrice: 0 + # config.txPool configures the transaction pool behaviour. txPool: + # config.txPool.type selects the transaction ordering algorithm (e.g., FIFO, SEQUENCED). type: SEQUENCED + # config.txPool.maxSize limits the total number of transactions kept in the pool. maxSize: 100000 + # config.txPool.noLocalPriority disables automatic prioritisation of locally submitted transactions. noLocalPriority: true + # config.txPool.limitByAccountPercentage caps per-account transaction contribution. limitByAccountPercentage: 1 + # config.txPool.enableSaveRestore enables persistence of the transaction pool across restarts. enableSaveRestore: true + # config.p2p configures peer-to-peer networking parameters. p2p: + # config.p2p.enabled toggles the Besu peer-to-peer subsystem. enabled: true + # config.p2p.discoveryEnabled controls participation in peer discovery. discoveryEnabled: true + # config.p2p.staticNodesFile points to the static nodes JSON file mounted from ConfigMaps. staticNodesFile: "/etc/besu/static-nodes.json" + # config.p2p.interface binds the network interface for P2P sockets. interface: "0.0.0.0" + # config.p2p.maxPeers caps the number of simultaneous peer connections. maxPeers: 25 + # config.rpc holds RPC subsystem configuration shared by HTTP and WebSocket servers. rpc: + # config.rpc.txFeecap limits the fee per gas for RPC-submitted transactions (0 disables the cap). txFeecap: 0 + # config.sync configures chain synchronisation behaviour. sync: + # config.sync.minPeers defines the minimum peers required before commencing sync. minPeers: 1 + # config.sync.mode selects the synchronisation mode (e.g., FULL, FAST, SNAP). mode: FULL + # config.remoteConnectionsLimitEnabled toggles limiting simultaneous remote JSON-RPC connections. remoteConnectionsLimitEnabled: false + # config.randomPeerPriorityEnabled shuffles peer priority to avoid fixed ordering bias. randomPeerPriorityEnabled: true + # config.receiptCompactionEnabled compacts receipt storage to save disk space. receiptCompactionEnabled: true + # config.hostAllowlist enumerates hosts authorised to reach the HTTP/WS RPC endpoints. hostAllowlist: - "*" + # config.http configures the HTTP JSON-RPC server. http: + # config.http.enabled toggles the HTTP JSON-RPC listener. enabled: true + # config.http.host binds the HTTP JSON-RPC listener interface. host: "0.0.0.0" + # config.http.api lists the JSON-RPC API namespaces enabled over HTTP. api: - DEBUG - ETH @@ -112,15 +157,24 @@ config: - PERM - TXPOOL - PLUGINS + # config.http.corsOrigins enumerates allowed CORS origins for HTTP requests. corsOrigins: - all + # config.http.authenticationEnabled toggles JWT authentication for HTTP requests. authenticationEnabled: false + # config.http.maxActiveConnections limits concurrent HTTP connections. maxActiveConnections: 2000 + # config.http.maxRequestContentLength caps the HTTP body size in bytes. maxRequestContentLength: 524288000 + # config.http.maxBatchSize limits the number of JSON-RPC requests permitted in a single batch. maxBatchSize: 512 + # config.ws configures the WebSocket JSON-RPC server. ws: + # config.ws.enabled toggles the WebSocket JSON-RPC listener. enabled: true + # config.ws.host binds the WebSocket listener interface. host: "0.0.0.0" + # config.ws.api lists the JSON-RPC API namespaces enabled over WebSockets. api: - DEBUG - ETH @@ -133,17 +187,28 @@ config: - PERM - TXPOOL - PLUGINS + # config.ws.authenticationEnabled toggles JWT authentication for WebSocket requests. authenticationEnabled: false + # config.ws.maxActiveConnections limits concurrent WebSocket connections. maxActiveConnections: 2000 + # config.ws.maxFrameSize caps the maximum WebSocket frame size in bytes. maxFrameSize: 2097152 + # config.graphql configures the optional GraphQL endpoint. graphql: + # config.graphql.enabled toggles the GraphQL server. enabled: true + # config.graphql.host binds the GraphQL server interface. host: "0.0.0.0" + # config.graphql.corsOrigins lists allowed origins for GraphQL requests. corsOrigins: - all + # config.metrics configures the Prometheus metrics subsystem. metrics: + # config.metrics.enabled toggles the Prometheus metrics exporter. enabled: true + # config.metrics.host binds the metrics endpoint interface. host: "0.0.0.0" + # config.metrics.categories lists the metrics categories exposed. categories: - BLOCKCHAIN - ETHEREUM @@ -156,93 +221,97 @@ config: - RPC - SYNCHRONIZER - TRANSACTION_POOL + # config.revertReasonEnabled toggles inclusion of revert reasons in RPC responses. revertReasonEnabled: true -# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +# ingress configures Kubernetes Ingress resources for publicly exposing RPC services. ingress: + # ingress.enabled toggles creation of an Ingress resource. enabled: false + # ingress.className sets the ingressClassName field for controller selection. className: "" + # ingress.annotations attaches metadata annotations for ingress controllers. annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" + # ingress.hosts lists host/path routing rules for the Ingress. hosts: - host: chart-example.local paths: - path: / pathType: ImplementationSpecific + # ingress.tls defines TLS secrets and hostnames for HTTPS termination. tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local -# -- Expose the service via gateway-api HTTPRoute -# Requires Gateway API resources and suitable controller installed within the cluster -# (see: https://gateway-api.sigs.k8s.io/guides/) +# httpRoute configures Gateway API HTTPRoute resources used with Gateway controllers. httpRoute: - # HTTPRoute enabled. + # httpRoute.enabled toggles rendering of an HTTPRoute resource. enabled: false - # HTTPRoute annotations. + # httpRoute.annotations applies annotations to the HTTPRoute manifest. annotations: {} - # Which Gateways this Route is attached to. + # httpRoute.parentRefs lists Gateway references that should attach this route. parentRefs: - name: gateway sectionName: http # namespace: default - # Hostnames matching HTTP header. + # httpRoute.hostnames enumerates HTTP hostnames matched by the route. hostnames: - chart-example.local - # List of rules and filters applied. + # httpRoute.rules defines match and filter blocks evaluated by the Gateway. rules: - matches: - path: type: PathPrefix value: /headers - # filters: - # - type: RequestHeaderModifier - # requestHeaderModifier: - # set: - # - name: My-Overwrite-Header - # value: this-is-the-only-value - # remove: - # - User-Agent - # - matches: - # - path: - # type: PathPrefix - # value: /echo - # headers: - # - name: version - # value: v2 + # filters can be enabled to mutate headers or traffic characteristics. + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # Additional matches can be added as needed. + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 -# -- Expose the service via OpenShift Route when running on OpenShift clusters -# This relies on the OpenShift router to make the network nodes reachable externally. +# openShiftRoute configures an OpenShift Route for clusters running the OpenShift router. openShiftRoute: + # openShiftRoute.enabled toggles creation of the Route resource. enabled: false - # Route annotations transmitted to the OpenShift router. + # openShiftRoute.annotations attaches metadata annotations for the Route. annotations: {} - # Custom host name for the route. Leave empty to let the cluster allocate one. + # openShiftRoute.host enforces a specific hostname; leave empty for automatic allocation. host: "" - # Optional path to scope traffic. + # openShiftRoute.path constrains routing to a specific URL path. path: "" - # Wildcard behaviour; defaults to "None" when left empty. + # openShiftRoute.wildcardPolicy configures wildcard host handling (None or Subdomain). wildcardPolicy: "" + # openShiftRoute.port configures the target service port used by the Route. port: - # Target service port exposed through the route. + # openShiftRoute.port.targetPort references the named service port exposed via the Route. targetPort: http + # openShiftRoute.to configures backend weighting for load balancing. to: - # Adjust when balancing against multiple services; default weight 100 is omitted from the manifest. + # openShiftRoute.to.weight adjusts traffic weighting across multiple service backends. weight: 100 - # Extra service backends following the RouteTargetReference schema. + # openShiftRoute.alternateBackends adds additional backend targets following RouteTargetReference schema. alternateBackends: [] - # TLS termination settings; set to null to disable, or provide termination, certificates, etc. + # openShiftRoute.tls defines TLS termination settings or disables TLS when set to null. tls: null +# resources defines CPU and memory requests/limits for Besu containers. resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi @@ -250,65 +319,90 @@ resources: # cpu: 100m # memory: 128Mi -# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ -# Besu publishes dedicated /liveness and /readiness endpoints on the JSON-RPC service port; probe timings cover startup warm-up. +# livenessProbe configures the HTTP probe used to determine if the container should be restarted. livenessProbe: + # livenessProbe.httpGet.path hits the Besu liveness endpoint. httpGet: path: /liveness + # livenessProbe.httpGet.port references the named container port serving HTTP JSON-RPC. port: json-rpc + # livenessProbe.initialDelaySeconds waits before starting liveness checks. initialDelaySeconds: 30 + # livenessProbe.periodSeconds schedules how frequently the probe runs. periodSeconds: 10 + # livenessProbe.timeoutSeconds caps how long the probe waits for a response. timeoutSeconds: 2 + # livenessProbe.failureThreshold restarts the pod after this many consecutive failures. failureThreshold: 3 + +# readinessProbe configures the HTTP probe used to advertise readiness to receive traffic. readinessProbe: + # readinessProbe.httpGet.path hits the readiness endpoint with peer and sync thresholds. httpGet: path: /readiness?minPeers=0&maxBlocksBehind=100 + # readinessProbe.httpGet.port references the named container port serving HTTP JSON-RPC. port: json-rpc + # readinessProbe.initialDelaySeconds waits before starting readiness checks. initialDelaySeconds: 15 + # readinessProbe.periodSeconds schedules how frequently the readiness probe runs. periodSeconds: 10 + # readinessProbe.timeoutSeconds caps the probe response time. timeoutSeconds: 2 + # readinessProbe.failureThreshold marks the pod unready after this many consecutive failures. failureThreshold: 3 -# Persistent storage configuration for the StatefulSet more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +# persistence configures persistent storage for validator and RPC nodes. persistence: + # persistence.enabled toggles persistent volume usage for ledger data. enabled: false - # When enabled without an existingClaim, a PersistentVolumeClaim is created per replica. + # persistence.mountPath is the container directory where the volume is mounted. mountPath: /data + # persistence.volumeName names the volume when using volumeClaimTemplates. volumeName: data + # persistence.accessModes declares the desired access modes for the PVC. accessModes: - ReadWriteOnce - # size is required when persistence.enabled is true and existingClaim is not set. Increase storage by resizing the bound PVC or switching to an existingClaim. + # persistence.size defines the requested storage capacity when provisioning PVCs. size: 20Gi + # persistence.storageClass selects the Kubernetes StorageClass; leave empty for default. storageClass: "" + # persistence.volumeMode switches between Filesystem and Block volume modes. volumeMode: "" - # Set existingClaim to reuse a pre-provisioned PVC that you manage (supports manual resizing). + # persistence.existingClaim references a pre-created PersistentVolumeClaim to reuse. existingClaim: "" - # Optional PVC metadata. + # persistence.annotations attaches metadata annotations to created PVCs. annotations: {} + # persistence.selector filters PersistentVolumes when binding static PVs. selector: {} + # persistence.retention configures how PVCs are handled during deletion or scale-down. retention: + # persistence.retention.whenDeleted dictates PVC behaviour when the Helm release is deleted. whenDeleted: Retain + # persistence.retention.whenScaled dictates PVC behaviour when replicas are scaled down. whenScaled: Delete - # Optional mount fine-tuning. + # persistence.subPath mounts a subdirectory of the volume instead of the root. subPath: "" + # persistence.readOnly mounts the volume read-only when true. readOnly: false - -# Additional volumes on the output StatefulSet definition. +# volumes lists extra volumes attached to each Besu pod for custom configuration or secrets. volumes: [] # - name: foo # secret: # secretName: mysecret # optional: false -# Additional volumeMounts on the output StatefulSet definition. +# volumeMounts lists additional container volume mounts beyond the defaults. volumeMounts: [] # - name: foo # mountPath: "/etc/foo" # readOnly: true +# nodeSelector constrains pod scheduling to nodes with matching labels. nodeSelector: {} +# tolerations allows pods to schedule onto nodes with corresponding taints. tolerations: [] +# affinity defines node and pod affinity/anti-affinity rules for advanced scheduling. affinity: {} From a7816e2caf5e7e505f13323d09ae839efeac5ffa Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 17:06:48 +0200 Subject: [PATCH 14/17] fix(charts): update values.yaml and README for network nodes - Added a comment in `values.yaml` to clarify that the parent chart does not define additional values and to guide users on overriding subchart defaults. - Made minor adjustments to the `README.md` for network nodes to ensure clarity and consistency in the documentation of configuration values. - Enhanced overall integrity and readability of the configuration files. --- charts/network/charts/network-nodes/README.md | 18 ++++++++++++++++-- charts/network/values.yaml | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/charts/network/charts/network-nodes/README.md b/charts/network/charts/network-nodes/README.md index 975efd9..28f5116 100644 --- a/charts/network/charts/network-nodes/README.md +++ b/charts/network/charts/network-nodes/README.md @@ -90,7 +90,13 @@ A Helm chart for Kubernetes | config.ws.maxActiveConnections | int | `2000` | | | config.ws.maxFrameSize | int | `2097152` | | | fullnameOverride | string | `"besu-node"` | | -| httpRoute | object | `{"annotations":{},"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"gateway","sectionName":"http"}],"rules":[{"matches":[{"path":{"type":"PathPrefix","value":"/headers"}}]}]}` | Expose the service via gateway-api HTTPRoute Requires Gateway API resources and suitable controller installed within the cluster (see: https://gateway-api.sigs.k8s.io/guides/) | +| httpRoute.annotations | object | `{}` | | +| httpRoute.enabled | bool | `false` | | +| httpRoute.hostnames[0] | string | `"chart-example.local"` | | +| httpRoute.parentRefs[0].name | string | `"gateway"` | | +| httpRoute.parentRefs[0].sectionName | string | `"http"` | | +| httpRoute.rules[0].matches[0].path.type | string | `"PathPrefix"` | | +| httpRoute.rules[0].matches[0].path.value | string | `"/headers"` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"docker.io/hyperledger/besu"` | | | image.tag | string | `"25.8.0"` | | @@ -110,7 +116,15 @@ A Helm chart for Kubernetes | livenessProbe.timeoutSeconds | int | `2` | | | nameOverride | string | `""` | | | nodeSelector | object | `{}` | | -| openShiftRoute | object | `{"alternateBackends":[],"annotations":{},"enabled":false,"host":"","path":"","port":{"targetPort":"http"},"tls":null,"to":{"weight":100},"wildcardPolicy":""}` | Expose the service via OpenShift Route when running on OpenShift clusters This relies on the OpenShift router to make the network nodes reachable externally. | +| openShiftRoute.alternateBackends | list | `[]` | | +| openShiftRoute.annotations | object | `{}` | | +| openShiftRoute.enabled | bool | `false` | | +| openShiftRoute.host | string | `""` | | +| openShiftRoute.path | string | `""` | | +| openShiftRoute.port.targetPort | string | `"http"` | | +| openShiftRoute.tls | string | `nil` | | +| openShiftRoute.to.weight | int | `100` | | +| openShiftRoute.wildcardPolicy | string | `""` | | | persistence.accessModes[0] | string | `"ReadWriteOnce"` | | | persistence.annotations | object | `{}` | | | persistence.enabled | bool | `false` | | diff --git a/charts/network/values.yaml b/charts/network/values.yaml index e69de29..84847fe 100644 --- a/charts/network/values.yaml +++ b/charts/network/values.yaml @@ -0,0 +1 @@ +# This parent chart does not define additional values; override subchart defaults here when packaging composite releases. From 13578678cac8731472f1079125541b33982d8979 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 17:23:17 +0200 Subject: [PATCH 15/17] fix(charts): update README and values.yaml for network-bootstrapper - Revised the README.md to improve clarity and consistency in the documentation of configuration values for the network-bootstrapper. - Updated the structure of configuration values in values.yaml to align with best practices, ensuring accurate documentation for users. - Enhanced overall integrity and readability of the configuration files, including detailed descriptions for settings like `clusterDomain`, `defaultStaticNodeDiscoveryPort`, and `fullnameOverride`. --- .../charts/network-bootstrapper/README.md | 62 ++-- .../charts/network-bootstrapper/values.yaml | 84 ++--- charts/network/charts/network-nodes/README.md | 257 ++++++-------- .../network/charts/network-nodes/values.yaml | 334 +++++++++--------- 4 files changed, 358 insertions(+), 379 deletions(-) diff --git a/charts/network/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md index 274d346..9a96869 100644 --- a/charts/network/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -15,40 +15,40 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | | -| clusterDomain | string | `"svc.cluster.local"` | | -| defaultStaticNodeDiscoveryPort | int | `30303` | | -| defaultStaticNodePort | int | `30303` | | -| fullnameOverride | string | `"bootstrapper"` | | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"ghcr.io/settlemint/network-bootstrapper"` | | -| image.tag | string | `""` | | -| imagePullSecrets | list | `[]` | | -| nameOverride | string | `""` | | +| clusterDomain | string | `"svc.cluster.local"` | Kubernetes DNS suffix appended to generated static node hostnames when unset by the CLI. | +| defaultStaticNodeDiscoveryPort | int | `30303` | Default UDP discovery port used in generated enode URIs when no override is provided. | +| defaultStaticNodePort | int | `30303` | Default TCP P2P port used in generated enode URIs when no override is provided. | +| fullnameOverride | string | `"bootstrapper"` | Override for the fully qualified resource name generated by helpers. | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy controlling when Kubernetes fetches updated image layers. | +| image.repository | string | `"ghcr.io/settlemint/network-bootstrapper"` | OCI registry path hosting the network bootstrapper image. | +| image.tag | string | `""` | Image tag override; leave empty to inherit the chart appVersion. | +| imagePullSecrets | list | `[]` | Image pull secrets enabling access to private registries. | +| nameOverride | string | `""` | Override for the short release name used by name templates. | | nodeSelector | object | `{}` | | | podAnnotations | object | `{}` | | | podLabels | object | `{}` | | | podSecurityContext | object | `{}` | | -| rbac.create | bool | `true` | | +| rbac.create | bool | `true` | Render Role and RoleBinding resources tied to the ServiceAccount. | | resources | object | `{}` | | | securityContext | object | `{}` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.automount | bool | `true` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| settings.allocations | string | `nil` | | -| settings.chainId | string | `nil` | | -| settings.consensus | string | `nil` | | -| settings.contractSizeLimit | string | `nil` | | -| settings.evmStackSize | string | `nil` | | -| settings.gasLimit | string | `nil` | | -| settings.gasPrice | string | `nil` | | -| settings.outputType | string | `"kubernetes"` | | -| settings.secondsPerBlock | string | `nil` | | -| settings.staticNodeDiscoveryPort | string | `nil` | | -| settings.staticNodeDomain | string | `nil` | | -| settings.staticNodeNamespace | string | `nil` | | -| settings.staticNodePort | string | `nil` | | -| settings.validators | string | `nil` | | -| tolerations | list | `[]` | | -| volumeMounts | list | `[]` | | -| volumes | list | `[]` | | +| serviceAccount.annotations | object | `{}` | Metadata annotations applied to the ServiceAccount. | +| serviceAccount.automount | bool | `true` | Mount the ServiceAccount token into the bootstrapper pod. | +| serviceAccount.create | bool | `true` | Create a ServiceAccount resource for the release automatically. | +| serviceAccount.name | string | `""` | Existing ServiceAccount name to reuse instead of creating one. | +| settings.allocations | string | `nil` | Path to a JSON allocations file providing pre-funded accounts. | +| settings.chainId | int | `nil` | Explicit chain ID to encode into the genesis file. | +| settings.consensus | string | `nil` | Consensus engine to configure (QBFT or IBFTv2). | +| settings.contractSizeLimit | int | `nil` | Maximum smart-contract bytecode size accepted by the EVM. | +| settings.evmStackSize | int | `nil` | Maximum EVM stack size permitted for contract execution. | +| settings.gasLimit | int | `nil` | Genesis block gas limit expressed as a decimal number. | +| settings.gasPrice | int | `nil` | Base gas price in wei applied across the network. | +| settings.outputType | string | `"kubernetes"` | Output target for generated artefacts: screen, file, or kubernetes. | +| settings.secondsPerBlock | int | `nil` | Block interval in seconds for the genesis configuration. | +| settings.staticNodeDiscoveryPort | int | `nil` | UDP discovery port embedded in static node enode URIs. | +| settings.staticNodeDomain | string | `nil` | DNS suffix appended to generated static node hostnames. | +| settings.staticNodeNamespace | string | `nil` | Namespace component inserted between service name and domain in static node hostnames. | +| settings.staticNodePort | int | `nil` | TCP P2P port embedded in static node enode URIs. | +| settings.validators | int | `nil` | Number of validator identities to generate (default 4). | +| tolerations | list | `[]` | Tolerations allowing the bootstrapper pod onto tainted nodes. | +| volumeMounts | list | `[]` | Extra volume mounts applied to the bootstrapper container. | +| volumes | list | `[]` | Additional volumes attached to the bootstrapper pod. | diff --git a/charts/network/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml index af372d8..b1c46ad 100644 --- a/charts/network/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -1,49 +1,49 @@ -# image configures the container image for the network bootstrapper job. +# Default configuration values for the network-bootstrapper subchart. Each key is annotated for helm-docs output. image: - # image.repository is the OCI registry path that hosts the bootstrapper image. + # -- (string) OCI registry path hosting the network bootstrapper image. repository: ghcr.io/settlemint/network-bootstrapper - # image.pullPolicy controls when Kubernetes re-pulls the bootstrapper image layers. + # -- (string) Image pull policy controlling when Kubernetes fetches updated image layers. pullPolicy: IfNotPresent - # image.tag overrides the image tag; leave empty to fall back to the chart appVersion. + # -- (string) Image tag override; leave empty to inherit the chart appVersion. tag: "" -# imagePullSecrets lists Kubernetes secrets used to authenticate against private registries. +# -- (list) Image pull secrets enabling access to private registries. imagePullSecrets: [] -# nameOverride replaces the short chart name used in resource naming helpers. +# -- (string) Override for the short release name used by name templates. nameOverride: "" -# fullnameOverride replaces the fully qualified resource name generated by the helpers template. +# -- (string) Override for the fully qualified resource name generated by helpers. fullnameOverride: "bootstrapper" -# serviceAccount encapsulates the ServiceAccount resource bound to the bootstrapper pod. +# ServiceAccount configuration associated with the bootstrapper job. serviceAccount: - # serviceAccount.create toggles creation of a dedicated ServiceAccount for this release. + # -- (bool) Create a ServiceAccount resource for the release automatically. create: true - # serviceAccount.automount determines whether the pod receives a projected API token. + # -- (bool) Mount the ServiceAccount token into the bootstrapper pod. automount: true - # serviceAccount.annotations adds metadata annotations to the generated (or referenced) ServiceAccount. + # -- (object) Metadata annotations applied to the ServiceAccount. annotations: {} - # serviceAccount.name supplies an existing ServiceAccount to reuse instead of creating one automatically. + # -- (string) Existing ServiceAccount name to reuse instead of creating one. name: "" -# rbac configures Role and RoleBinding manifests required for Kubernetes output mode. +# RBAC settings that provision Role and RoleBinding for Kubernetes output. rbac: - # rbac.create controls whether RBAC resources are rendered for the bootstrapper ServiceAccount. + # -- (bool) Render Role and RoleBinding resources tied to the ServiceAccount. create: true -# podAnnotations adds custom annotations to the bootstrapper pod template metadata. +# Additional annotations merged onto the bootstrapper pod template. podAnnotations: {} -# podLabels adds custom labels to the bootstrapper pod template metadata. +# Extra labels merged onto the bootstrapper pod template. podLabels: {} -# podSecurityContext defines default security context attributes applied at the pod level. +# Pod-level security context applied to all containers in the pod. podSecurityContext: {} # fsGroup: 2000 -# securityContext defines the container security context for the bootstrapper container. +# Container security context applied to the bootstrapper container. securityContext: {} # capabilities: @@ -53,7 +53,7 @@ securityContext: # runAsNonRoot: true # runAsUser: 1000 -# resources specifies CPU and memory requests or limits for the bootstrapper container. +# CPU and memory requests or limits for the bootstrapper container. resources: {} # limits: @@ -63,64 +63,64 @@ resources: # cpu: 100m # memory: 128Mi -# volumes lists extra volumes attached to the bootstrapper pod for custom inputs or outputs. +# -- (list) Additional volumes attached to the bootstrapper pod. volumes: [] # - name: foo # secret: # secretName: mysecret # optional: false -# volumeMounts lists additional volume mounts injected into the bootstrapper container definition. +# -- (list) Extra volume mounts applied to the bootstrapper container. volumeMounts: [] # - name: foo # mountPath: "/etc/foo" # readOnly: true -# nodeSelector constrains scheduling of the bootstrapper pod to nodes with matching labels. +# Node selector constraints for scheduling the bootstrapper pod. nodeSelector: {} -# tolerations allows the bootstrapper pod to schedule onto tainted nodes. +# -- (list) Tolerations allowing the bootstrapper pod onto tainted nodes. tolerations: [] -# affinity defines node or pod affinity rules that steer bootstrapper pod placement. +# Affinity and anti-affinity rules influencing pod placement. affinity: {} -# clusterDomain is the Kubernetes DNS suffix appended when generating static node hostnames. +# -- (string) Kubernetes DNS suffix appended to generated static node hostnames when unset by the CLI. clusterDomain: svc.cluster.local -# defaultStaticNodePort is the Besu P2P TCP port used in generated enode URIs when not overridden. +# -- (int) Default TCP P2P port used in generated enode URIs when no override is provided. defaultStaticNodePort: 30303 -# defaultStaticNodeDiscoveryPort is the Besu UDP discovery port used in generated enode URIs when not overridden. +# -- (int) Default UDP discovery port used in generated enode URIs when no override is provided. defaultStaticNodeDiscoveryPort: 30303 -# settings maps to CLI flags for the bootstrapper command executed by the job. +# Settings passed to the network-bootstrapper CLI executed by the job. settings: - # settings.validators controls how many validator identities the bootstrapper produces (default 4). + # -- (int) Number of validator identities to generate (default 4). validators: - # settings.staticNodeDomain overrides the DNS suffix appended to generated static node hostnames. + # -- (string) DNS suffix appended to generated static node hostnames. staticNodeDomain: - # settings.staticNodeNamespace overrides the namespace segment embedded into static node hostnames. + # -- (string) Namespace component inserted between service name and domain in static node hostnames. staticNodeNamespace: - # settings.staticNodePort overrides the TCP P2P port encoded in static node enode URIs. + # -- (int) TCP P2P port embedded in static node enode URIs. staticNodePort: - # settings.staticNodeDiscoveryPort overrides the UDP discovery port encoded in static node enode URIs. + # -- (int) UDP discovery port embedded in static node enode URIs. staticNodeDiscoveryPort: - # settings.allocations provides a path to a JSON allocations file that seeds pre-funded accounts. + # -- (string) Path to a JSON allocations file providing pre-funded accounts. allocations: - # settings.outputType selects where generated artefacts are written: screen, file, or kubernetes. + # -- (string) Output target for generated artefacts: screen, file, or kubernetes. outputType: kubernetes - # settings.consensus selects the Besu consensus engine (QBFT or IBFTv2). + # -- (string) Consensus engine to configure (QBFT or IBFTv2). consensus: - # settings.chainId overrides the randomly generated chain ID used in the genesis file. + # -- (int) Explicit chain ID to encode into the genesis file. chainId: - # settings.secondsPerBlock sets the target block interval encoded in genesis. + # -- (int) Block interval in seconds for the genesis configuration. secondsPerBlock: - # settings.gasLimit sets the genesis block gas limit in decimal format. + # -- (int) Genesis block gas limit expressed as a decimal number. gasLimit: - # settings.gasPrice sets the base gas price in wei recorded in genesis metadata. + # -- (int) Base gas price in wei applied across the network. gasPrice: - # settings.evmStackSize sets the maximum EVM stack size available to smart contracts. + # -- (int) Maximum EVM stack size permitted for contract execution. evmStackSize: - # settings.contractSizeLimit sets the maximum contract bytecode size permitted by the EVM. + # -- (int) Maximum smart-contract bytecode size accepted by the EVM. contractSizeLimit: diff --git a/charts/network/charts/network-nodes/README.md b/charts/network/charts/network-nodes/README.md index 28f5116..2d33913 100644 --- a/charts/network/charts/network-nodes/README.md +++ b/charts/network/charts/network-nodes/README.md @@ -15,157 +15,126 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | | -| config.bonsaiLimitTrieLogsEnabled | bool | `false` | | -| config.cacheLastBlocks | int | `1024` | | -| config.dataStorageFormat | string | `"FOREST"` | | -| config.graphql.corsOrigins[0] | string | `"all"` | | -| config.graphql.enabled | bool | `true` | | -| config.graphql.host | string | `"0.0.0.0"` | | -| config.hostAllowlist[0] | string | `"*"` | | -| config.http.api[0] | string | `"DEBUG"` | | -| config.http.api[10] | string | `"PLUGINS"` | | -| config.http.api[1] | string | `"ETH"` | | -| config.http.api[2] | string | `"ADMIN"` | | -| config.http.api[3] | string | `"WEB3"` | | -| config.http.api[4] | string | `"IBFT"` | | -| config.http.api[5] | string | `"NET"` | | -| config.http.api[6] | string | `"TRACE"` | | -| config.http.api[7] | string | `"QBFT"` | | -| config.http.api[8] | string | `"PERM"` | | -| config.http.api[9] | string | `"TXPOOL"` | | -| config.http.authenticationEnabled | bool | `false` | | -| config.http.corsOrigins[0] | string | `"all"` | | -| config.http.enabled | bool | `true` | | -| config.http.host | string | `"0.0.0.0"` | | -| config.http.maxActiveConnections | int | `2000` | | -| config.http.maxBatchSize | int | `512` | | -| config.http.maxRequestContentLength | int | `524288000` | | -| config.logging | string | `"INFO"` | | -| config.metrics.categories[0] | string | `"BLOCKCHAIN"` | | -| config.metrics.categories[10] | string | `"TRANSACTION_POOL"` | | -| config.metrics.categories[1] | string | `"ETHEREUM"` | | -| config.metrics.categories[2] | string | `"EXECUTORS"` | | -| config.metrics.categories[3] | string | `"JVM"` | | -| config.metrics.categories[4] | string | `"NETWORK"` | | -| config.metrics.categories[5] | string | `"PEERS"` | | -| config.metrics.categories[6] | string | `"PROCESS"` | | -| config.metrics.categories[7] | string | `"PRUNER"` | | -| config.metrics.categories[8] | string | `"RPC"` | | -| config.metrics.categories[9] | string | `"SYNCHRONIZER"` | | -| config.metrics.enabled | bool | `true` | | -| config.metrics.host | string | `"0.0.0.0"` | | -| config.minGasPrice | int | `0` | | -| config.p2p.discoveryEnabled | bool | `true` | | -| config.p2p.enabled | bool | `true` | | -| config.p2p.interface | string | `"0.0.0.0"` | | -| config.p2p.maxPeers | int | `25` | | -| config.p2p.staticNodesFile | string | `"/etc/besu/static-nodes.json"` | | -| config.privateKeyFilename | string | `"privateKey"` | | -| config.randomPeerPriorityEnabled | bool | `true` | | -| config.receiptCompactionEnabled | bool | `true` | | -| config.remoteConnectionsLimitEnabled | bool | `false` | | -| config.revertReasonEnabled | bool | `true` | | -| config.rpc.txFeecap | int | `0` | | -| config.sync.minPeers | int | `1` | | -| config.sync.mode | string | `"FULL"` | | -| config.txPool.enableSaveRestore | bool | `true` | | -| config.txPool.limitByAccountPercentage | int | `1` | | -| config.txPool.maxSize | int | `100000` | | -| config.txPool.noLocalPriority | bool | `true` | | -| config.txPool.type | string | `"SEQUENCED"` | | -| config.ws.api[0] | string | `"DEBUG"` | | -| config.ws.api[10] | string | `"PLUGINS"` | | -| config.ws.api[1] | string | `"ETH"` | | -| config.ws.api[2] | string | `"ADMIN"` | | -| config.ws.api[3] | string | `"WEB3"` | | -| config.ws.api[4] | string | `"IBFT"` | | -| config.ws.api[5] | string | `"NET"` | | -| config.ws.api[6] | string | `"TRACE"` | | -| config.ws.api[7] | string | `"QBFT"` | | -| config.ws.api[8] | string | `"PERM"` | | -| config.ws.api[9] | string | `"TXPOOL"` | | -| config.ws.authenticationEnabled | bool | `false` | | -| config.ws.enabled | bool | `true` | | -| config.ws.host | string | `"0.0.0.0"` | | -| config.ws.maxActiveConnections | int | `2000` | | -| config.ws.maxFrameSize | int | `2097152` | | -| fullnameOverride | string | `"besu-node"` | | +| config.bonsaiLimitTrieLogsEnabled | bool | `false` | Emit Bonsai limit trie logs for debugging state transitions. | +| config.cacheLastBlocks | int | `1024` | Number of recent blocks cached in memory. | +| config.dataStorageFormat | string | `"FOREST"` | Ledger storage backend (FOREST or BONSAI). | +| config.graphql.corsOrigins | list | `["all"]` | Allowed CORS origins for GraphQL requests. | +| config.graphql.enabled | bool | `true` | Enable the GraphQL API server. | +| config.graphql.host | string | `"0.0.0.0"` | Network interface for the GraphQL server. | +| config.hostAllowlist | list | `["*"]` | Hostnames allowed to access the RPC interfaces; `*` permits all. | +| config.http.api | list | `["DEBUG","ETH","ADMIN","WEB3","IBFT","NET","TRACE","QBFT","PERM","TXPOOL","PLUGINS"]` | Enabled JSON-RPC API namespaces exposed over HTTP. | +| config.http.authenticationEnabled | bool | `false` | Enable JWT authentication for HTTP JSON-RPC requests. | +| config.http.corsOrigins | list | `["all"]` | Allowed CORS origins for HTTP JSON-RPC requests. | +| config.http.enabled | bool | `true` | Enable the HTTP JSON-RPC listener. | +| config.http.host | string | `"0.0.0.0"` | Network interface for the HTTP JSON-RPC listener. | +| config.http.maxActiveConnections | int | `2000` | Maximum concurrent HTTP JSON-RPC connections. | +| config.http.maxBatchSize | int | `512` | Maximum number of batched JSON-RPC calls per request. | +| config.http.maxRequestContentLength | int | `524288000` | Maximum HTTP request body size in bytes. | +| config.logging | string | `"INFO"` | Log verbosity level for Besu components. | +| config.metrics.categories | list | `["BLOCKCHAIN","ETHEREUM","EXECUTORS","JVM","NETWORK","PEERS","PROCESS","PRUNER","RPC","SYNCHRONIZER","TRANSACTION_POOL"]` | Metrics categories exposed to Prometheus. | +| config.metrics.enabled | bool | `true` | Enable the Prometheus metrics endpoint. | +| config.metrics.host | string | `"0.0.0.0"` | Network interface for the metrics endpoint. | +| config.minGasPrice | int | `0` | Minimum gas price accepted for transactions (wei). | +| config.p2p.discoveryEnabled | bool | `true` | Enable the discovery protocol for automatic peer finding. | +| config.p2p.enabled | bool | `true` | Enable the devp2p networking subsystem. | +| config.p2p.interface | string | `"0.0.0.0"` | Network interface the P2P server binds to. | +| config.p2p.maxPeers | int | `25` | Maximum simultaneous peer connections. | +| config.p2p.staticNodesFile | string | `"/etc/besu/static-nodes.json"` | Path to the static-nodes.json file providing fixed peers. | +| config.privateKeyFilename | string | `"privateKey"` | Filename containing each node's private key within mounted secrets. | +| config.randomPeerPriorityEnabled | bool | `true` | Randomise peer priority to avoid deterministic ordering. | +| config.receiptCompactionEnabled | bool | `true` | Enable receipt compaction to reduce disk usage. | +| config.remoteConnectionsLimitEnabled | bool | `false` | Enable limits on concurrent remote JSON-RPC connections. | +| config.revertReasonEnabled | bool | `true` | Include revert reasons in RPC responses when failures occur. | +| config.rpc.txFeecap | int | `0` | Maximum fee per gas accepted for transactions submitted via RPC (0 disables the cap). | +| config.sync.minPeers | int | `1` | Minimum number of peers required before synchronisation starts. | +| config.sync.mode | string | `"FULL"` | Synchronisation mode (FULL, FAST, SNAP, etc.). | +| config.txPool.enableSaveRestore | bool | `true` | Persist the transaction pool to disk between restarts. | +| config.txPool.limitByAccountPercentage | int | `1` | Maximum percentage of the pool allowed per account. | +| config.txPool.maxSize | int | `100000` | Maximum transactions retained in the pool. | +| config.txPool.noLocalPriority | bool | `true` | Disable preferential treatment of locally submitted transactions. | +| config.txPool.type | string | `"SEQUENCED"` | Transaction ordering strategy (e.g., SEQUENCED). | +| config.ws.api | list | `["DEBUG","ETH","ADMIN","WEB3","IBFT","NET","TRACE","QBFT","PERM","TXPOOL","PLUGINS"]` | Enabled JSON-RPC API namespaces exposed over WebSockets. | +| config.ws.authenticationEnabled | bool | `false` | Enable JWT authentication for WebSocket requests. | +| config.ws.enabled | bool | `true` | Enable the WebSocket JSON-RPC listener. | +| config.ws.host | string | `"0.0.0.0"` | Network interface for the WebSocket listener. | +| config.ws.maxActiveConnections | int | `2000` | Maximum concurrent WebSocket connections. | +| config.ws.maxFrameSize | int | `2097152` | Maximum WebSocket frame size in bytes. | +| fullnameOverride | string | `"besu-node"` | Override for the fully qualified release name used in resource naming. | | httpRoute.annotations | object | `{}` | | -| httpRoute.enabled | bool | `false` | | -| httpRoute.hostnames[0] | string | `"chart-example.local"` | | -| httpRoute.parentRefs[0].name | string | `"gateway"` | | -| httpRoute.parentRefs[0].sectionName | string | `"http"` | | -| httpRoute.rules[0].matches[0].path.type | string | `"PathPrefix"` | | -| httpRoute.rules[0].matches[0].path.value | string | `"/headers"` | | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `"docker.io/hyperledger/besu"` | | -| image.tag | string | `"25.8.0"` | | -| imagePullSecrets | list | `[]` | | +| httpRoute.enabled | bool | `false` | Enable rendering of an HTTPRoute resource. | +| httpRoute.hostnames | list | `["chart-example.local"]` | HTTP hostnames matched by the route. | +| httpRoute.parentRefs | list | `[{"name":"gateway","sectionName":"http"}]` | Gateway references that should accept this route. | +| httpRoute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/headers"}}]}]` | Rules containing matches and optional filters evaluated by the Gateway. | +| httpRoute.rules[0].matches | list | `[{"path":{"type":"PathPrefix","value":"/headers"}}]` | Match conditions evaluated for each request. | +| httpRoute.rules[0].matches[0].path.type | string | `"PathPrefix"` | Path match type (Exact, PathPrefix, or RegularExpression). | +| httpRoute.rules[0].matches[0].path.value | string | `"/headers"` | Path value used when evaluating the request URL. | +| image.pullPolicy | string | `"IfNotPresent"` | Kubernetes image pull policy for Besu containers. | +| image.repository | string | `"docker.io/hyperledger/besu"` | OCI image repository hosting Hyperledger Besu. | +| image.tag | string | `"25.8.0"` | Specific Besu image tag to deploy. | +| imagePullSecrets | list | `[]` | Image pull secrets granting registry access for the Besu image. | | ingress.annotations | object | `{}` | | -| ingress.className | string | `""` | | -| ingress.enabled | bool | `false` | | -| ingress.hosts[0].host | string | `"chart-example.local"` | | -| ingress.hosts[0].paths[0].path | string | `"/"` | | -| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | -| ingress.tls | list | `[]` | | -| livenessProbe.failureThreshold | int | `3` | | -| livenessProbe.httpGet.path | string | `"/liveness"` | | -| livenessProbe.httpGet.port | string | `"json-rpc"` | | -| livenessProbe.initialDelaySeconds | int | `30` | | -| livenessProbe.periodSeconds | int | `10` | | -| livenessProbe.timeoutSeconds | int | `2` | | -| nameOverride | string | `""` | | +| ingress.className | string | `""` | ingressClassName assigned to the Ingress for controller selection. | +| ingress.enabled | bool | `false` | Enable creation of an Ingress resource. | +| ingress.hosts | list | `[{"host":"chart-example.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}]` | Hostname and path routing rules for the Ingress. | +| ingress.tls | list | `[]` | TLS configuration for Ingress hosts. | +| livenessProbe.failureThreshold | int | `3` | Consecutive failures required before the container is restarted. | +| livenessProbe.httpGet.path | string | `"/liveness"` | HTTP path used for liveness probing. | +| livenessProbe.httpGet.port | string|int | `"json-rpc"` | Target container port serving the liveness endpoint. | +| livenessProbe.initialDelaySeconds | int | `30` | Seconds to wait before starting liveness checks. | +| livenessProbe.periodSeconds | int | `10` | Frequency of liveness checks in seconds. | +| livenessProbe.timeoutSeconds | int | `2` | Timeout in seconds before marking the probe as failed. | +| nameOverride | string | `""` | Override for the short chart name used in resource naming. | | nodeSelector | object | `{}` | | -| openShiftRoute.alternateBackends | list | `[]` | | +| openShiftRoute.alternateBackends | list | `[]` | Additional backend references to balance traffic across services. | | openShiftRoute.annotations | object | `{}` | | -| openShiftRoute.enabled | bool | `false` | | -| openShiftRoute.host | string | `""` | | -| openShiftRoute.path | string | `""` | | -| openShiftRoute.port.targetPort | string | `"http"` | | -| openShiftRoute.tls | string | `nil` | | -| openShiftRoute.to.weight | int | `100` | | -| openShiftRoute.wildcardPolicy | string | `""` | | -| persistence.accessModes[0] | string | `"ReadWriteOnce"` | | +| openShiftRoute.enabled | bool | `false` | Enable creation of an OpenShift Route resource. | +| openShiftRoute.host | string | `""` | Desired external hostname for the Route; leave empty for automatic assignment. | +| openShiftRoute.path | string | `""` | URL path prefix handled by the Route. | +| openShiftRoute.port.targetPort | string | `"http"` | Named service port exposed through the Route. | +| openShiftRoute.tls | object|null | `nil` | TLS termination settings; set to null to disable TLS. | +| openShiftRoute.to.weight | int | `100` | Backend weight determining traffic distribution. | +| openShiftRoute.wildcardPolicy | string | `""` | Wildcard policy controlling subdomain routing (None or Subdomain). | +| persistence.accessModes | list | `["ReadWriteOnce"]` | Requested access modes for the PersistentVolumeClaim. | | persistence.annotations | object | `{}` | | -| persistence.enabled | bool | `false` | | -| persistence.existingClaim | string | `""` | | -| persistence.mountPath | string | `"/data"` | | -| persistence.readOnly | bool | `false` | | -| persistence.retention.whenDeleted | string | `"Retain"` | | -| persistence.retention.whenScaled | string | `"Delete"` | | +| persistence.enabled | bool | `false` | Enable persistent volume claims for ledger data. | +| persistence.existingClaim | string | `""` | Name of an existing PersistentVolumeClaim to reuse instead of creating new PVCs. | +| persistence.mountPath | string | `"/data"` | Container path where the persistent volume is mounted. | +| persistence.readOnly | bool | `false` | Mount the volume read-only when true. | +| persistence.retention.whenDeleted | string | `"Retain"` | Behaviour of PVCs when the Helm release is deleted. | +| persistence.retention.whenScaled | string | `"Delete"` | Behaviour of PVCs when the StatefulSet scales down. | | persistence.selector | object | `{}` | | -| persistence.size | string | `"20Gi"` | | -| persistence.storageClass | string | `""` | | -| persistence.subPath | string | `""` | | -| persistence.volumeMode | string | `""` | | -| persistence.volumeName | string | `"data"` | | -| podAnnotations."prometheus.io/path" | string | `"/metrics"` | | -| podAnnotations."prometheus.io/port" | string | `"9545"` | | -| podAnnotations."prometheus.io/scheme" | string | `"http"` | | -| podAnnotations."prometheus.io/scrape" | string | `"true"` | | +| persistence.size | string | `"20Gi"` | Requested storage capacity for each PersistentVolumeClaim. | +| persistence.storageClass | string | `""` | StorageClass name used for provisioning volumes; empty selects the cluster default. | +| persistence.subPath | string | `""` | Subdirectory of the volume to mount instead of the root. | +| persistence.volumeMode | string | `""` | Volume mode (Filesystem or Block) requested for the PVC. | +| persistence.volumeName | string | `"data"` | Logical name for the volume when using volumeClaimTemplates. | +| podAnnotations."prometheus.io/path" | string | `"/metrics"` | HTTP path exposing Prometheus-formatted metrics. | +| podAnnotations."prometheus.io/port" | string | `"9545"` | Container port value used by Prometheus to scrape metrics. | +| podAnnotations."prometheus.io/scheme" | string | `"http"` | HTTP scheme (http or https) used for metrics scraping. | +| podAnnotations."prometheus.io/scrape" | string | `"true"` | Enables Prometheus scraping of the Besu metrics endpoint. | | podLabels | object | `{}` | | | podSecurityContext | object | `{}` | | -| readinessProbe.failureThreshold | int | `3` | | -| readinessProbe.httpGet.path | string | `"/readiness?minPeers=0&maxBlocksBehind=100"` | | -| readinessProbe.httpGet.port | string | `"json-rpc"` | | -| readinessProbe.initialDelaySeconds | int | `15` | | -| readinessProbe.periodSeconds | int | `10` | | -| readinessProbe.timeoutSeconds | int | `2` | | +| readinessProbe.failureThreshold | int | `3` | Consecutive failures required before the pod is considered unready. | +| readinessProbe.httpGet.path | string | `"/readiness?minPeers=0&maxBlocksBehind=100"` | HTTP path used for readiness probing, including peer/sync thresholds. | +| readinessProbe.httpGet.port | string|int | `"json-rpc"` | Target container port serving the readiness endpoint. | +| readinessProbe.initialDelaySeconds | int | `15` | Seconds to wait before starting readiness checks. | +| readinessProbe.periodSeconds | int | `10` | Frequency of readiness checks in seconds. | +| readinessProbe.timeoutSeconds | int | `2` | Timeout in seconds before marking the probe as failed. | | resources | object | `{}` | | -| rpcReplicaCount | int | `2` | | +| rpcReplicaCount | int | `2` | Number of RPC node replicas provisioned via StatefulSet. | | securityContext | object | `{}` | | -| service.ports.discovery | int | `30303` | | -| service.ports.graphql | int | `8547` | | -| service.ports.metrics | int | `9545` | | -| service.ports.rlpx | int | `30303` | | -| service.ports.rpc | int | `8545` | | -| service.ports.ws | int | `8546` | | -| service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.automount | bool | `true` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| tolerations | list | `[]` | | -| validatorReplicaCount | int | `4` | | -| volumeMounts | list | `[]` | | -| volumes | list | `[]` | | +| service.ports.discovery | int | `30303` | Discovery UDP port used by devp2p. | +| service.ports.graphql | int | `8547` | GraphQL API port. | +| service.ports.metrics | int | `9545` | Prometheus metrics port. | +| service.ports.rlpx | int | `30303` | RLPx TCP peer-to-peer port. | +| service.ports.rpc | int | `8545` | HTTP JSON-RPC port. | +| service.ports.ws | int | `8546` | WebSocket JSON-RPC port. | +| service.type | string | `"ClusterIP"` | Service type controlling how endpoints are published (ClusterIP, NodePort, etc.). | +| serviceAccount.annotations | object | `{}` | Metadata annotations applied to the ServiceAccount. | +| serviceAccount.automount | bool | `true` | Mount the ServiceAccount token volume into pods. | +| serviceAccount.create | bool | `true` | Create a ServiceAccount resource automatically for the release. | +| serviceAccount.name | string | `""` | Existing ServiceAccount name to reuse when creation is disabled. | +| tolerations | list | `[]` | Tolerations allowing pods to run on nodes with matching taints. | +| validatorReplicaCount | int | `4` | Number of validator node replicas participating in consensus. | +| volumeMounts | list | `[]` | Additional volume mounts applied to Besu containers. | +| volumes | list | `[]` | Extra volumes attached to Besu pods for custom configuration or secrets. | diff --git a/charts/network/charts/network-nodes/values.yaml b/charts/network/charts/network-nodes/values.yaml index 13a8ea2..b3d2c0c 100644 --- a/charts/network/charts/network-nodes/values.yaml +++ b/charts/network/charts/network-nodes/values.yaml @@ -1,58 +1,60 @@ -# rpcReplicaCount sets the number of RPC StatefulSet replicas that serve JSON-RPC traffic. +# Default configuration values for the network-nodes subchart. All keys include helm-docs compatible annotations. + +# -- (int) Number of RPC node replicas provisioned via StatefulSet. rpcReplicaCount: 2 -# validatorReplicaCount sets the number of validator StatefulSet replicas responsible for consensus. +# -- (int) Number of validator node replicas participating in consensus. validatorReplicaCount: 4 -# image configures the Besu container image used for both RPC and validator pods. +# Container image configuration shared by validator and RPC pods. image: - # image.repository points at the OCI registry location containing the Besu image. + # -- (string) OCI image repository hosting Hyperledger Besu. repository: docker.io/hyperledger/besu - # image.pullPolicy determines when Kubernetes attempts to re-pull the image layers. + # -- (string) Kubernetes image pull policy for Besu containers. pullPolicy: IfNotPresent - # image.tag pins the Besu version; update to consume a different upstream release. + # -- (string) Specific Besu image tag to deploy. tag: "25.8.0" -# imagePullSecrets lists Docker registry credentials needed to pull the Besu image. +# -- (list) Image pull secrets granting registry access for the Besu image. imagePullSecrets: [] -# nameOverride replaces the short chart name used in resource name templates. +# -- (string) Override for the short chart name used in resource naming. nameOverride: "" -# fullnameOverride replaces the fully qualified release name used for resource naming. +# -- (string) Override for the fully qualified release name used in resource naming. fullnameOverride: "besu-node" -# serviceAccount configures the Kubernetes ServiceAccount bound to pods created by this chart. +# Kubernetes ServiceAccount configuration applied to Besu pods. serviceAccount: - # serviceAccount.create controls whether the chart should create a ServiceAccount automatically. + # -- (bool) Create a ServiceAccount resource automatically for the release. create: true - # serviceAccount.automount controls whether pods receive a projected ServiceAccount token. + # -- (bool) Mount the ServiceAccount token volume into pods. automount: true - # serviceAccount.annotations attaches metadata annotations to the ServiceAccount object. + # -- (object) Metadata annotations applied to the ServiceAccount. annotations: {} - # serviceAccount.name references an existing ServiceAccount when creation is disabled. + # -- (string) Existing ServiceAccount name to reuse when creation is disabled. name: "" -# podAnnotations applies metadata annotations to every Besu pod (validators and RPC nodes). +# Pod annotations merged onto all Besu pods. podAnnotations: - # podAnnotations.prometheus.io/scrape enables Prometheus autoscraping of Besu metrics. + # -- (string) Enables Prometheus scraping of the Besu metrics endpoint. prometheus.io/scrape: "true" - # podAnnotations.prometheus.io/path identifies the HTTP path exposing metrics. + # -- (string) HTTP path exposing Prometheus-formatted metrics. prometheus.io/path: /metrics - # podAnnotations.prometheus.io/port sets the metrics port label consumed by the scraper. + # -- (string) Container port value used by Prometheus to scrape metrics. prometheus.io/port: "9545" - # podAnnotations.prometheus.io/scheme selects the HTTP scheme used to scrape metrics. + # -- (string) HTTP scheme (http or https) used for metrics scraping. prometheus.io/scheme: http -# podLabels applies extra Kubernetes labels to every Besu pod for selection or organisation. +# Additional labels applied to all Besu pods. podLabels: {} -# podSecurityContext defines pod-level Linux security settings shared by all containers in a pod. +# Pod-level security context shared by all containers. podSecurityContext: {} # fsGroup: 2000 -# securityContext defines container-level security controls for the Besu process. +# Container-level security context applied to Besu containers. securityContext: {} # capabilities: @@ -62,89 +64,89 @@ securityContext: # runAsNonRoot: true # runAsUser: 1000 -# service defines the Kubernetes Service exposing Besu endpoints. +# Kubernetes Service definition exposing Besu endpoints. service: - # service.type selects the Service controller type (ClusterIP, NodePort, LoadBalancer, etc.). + # -- (string) Service type controlling how endpoints are published (ClusterIP, NodePort, etc.). type: ClusterIP - # service.ports maps friendly names to Service ports for Besu endpoints. + # Named Service ports exposing Besu interfaces. ports: - # service.ports.rpc exposes the HTTP JSON-RPC interface. + # -- (int) HTTP JSON-RPC port. rpc: 8545 - # service.ports.ws exposes the WebSocket JSON-RPC interface. + # -- (int) WebSocket JSON-RPC port. ws: 8546 - # service.ports.graphql exposes the GraphQL API. + # -- (int) GraphQL API port. graphql: 8547 - # service.ports.rlpx exposes the peer-to-peer RLPx TCP port. + # -- (int) RLPx TCP peer-to-peer port. rlpx: 30303 - # service.ports.discovery exposes the peer discovery UDP port. + # -- (int) Discovery UDP port used by devp2p. discovery: 30303 - # service.ports.metrics exposes the Prometheus metrics endpoint. + # -- (int) Prometheus metrics port. metrics: 9545 -# config captures the default besu.conf values applied to both validator and RPC nodes. +# Core Besu configuration persisted as besu.conf for both validators and RPC nodes. config: - # config.logging controls the global logging verbosity for Besu. + # -- (string) Log verbosity level for Besu components. logging: INFO - # config.dataStorageFormat selects the ledger storage backend (FOREST or BONSAI). + # -- (string) Ledger storage backend (FOREST or BONSAI). dataStorageFormat: FOREST - # config.privateKeyFilename names the file containing the node's private key inside mounted secrets. + # -- (string) Filename containing each node's private key within mounted secrets. privateKeyFilename: privateKey - # config.bonsaiLimitTrieLogsEnabled toggles Bonsai limit trie logging for debugging state transitions. + # -- (bool) Emit Bonsai limit trie logs for debugging state transitions. bonsaiLimitTrieLogsEnabled: false - # config.cacheLastBlocks specifies how many recent blocks are cached for quick access. + # -- (int) Number of recent blocks cached in memory. cacheLastBlocks: 1024 - # config.minGasPrice sets the minimum gas price accepted for transactions. + # -- (int) Minimum gas price accepted for transactions (wei). minGasPrice: 0 - # config.txPool configures the transaction pool behaviour. + # Transaction pool configuration for ingress and prioritisation. txPool: - # config.txPool.type selects the transaction ordering algorithm (e.g., FIFO, SEQUENCED). + # -- (string) Transaction ordering strategy (e.g., SEQUENCED). type: SEQUENCED - # config.txPool.maxSize limits the total number of transactions kept in the pool. + # -- (int) Maximum transactions retained in the pool. maxSize: 100000 - # config.txPool.noLocalPriority disables automatic prioritisation of locally submitted transactions. + # -- (bool) Disable preferential treatment of locally submitted transactions. noLocalPriority: true - # config.txPool.limitByAccountPercentage caps per-account transaction contribution. + # -- (int) Maximum percentage of the pool allowed per account. limitByAccountPercentage: 1 - # config.txPool.enableSaveRestore enables persistence of the transaction pool across restarts. + # -- (bool) Persist the transaction pool to disk between restarts. enableSaveRestore: true - # config.p2p configures peer-to-peer networking parameters. + # Peer-to-peer networking settings. p2p: - # config.p2p.enabled toggles the Besu peer-to-peer subsystem. + # -- (bool) Enable the devp2p networking subsystem. enabled: true - # config.p2p.discoveryEnabled controls participation in peer discovery. + # -- (bool) Enable the discovery protocol for automatic peer finding. discoveryEnabled: true - # config.p2p.staticNodesFile points to the static nodes JSON file mounted from ConfigMaps. + # -- (string) Path to the static-nodes.json file providing fixed peers. staticNodesFile: "/etc/besu/static-nodes.json" - # config.p2p.interface binds the network interface for P2P sockets. + # -- (string) Network interface the P2P server binds to. interface: "0.0.0.0" - # config.p2p.maxPeers caps the number of simultaneous peer connections. + # -- (int) Maximum simultaneous peer connections. maxPeers: 25 - # config.rpc holds RPC subsystem configuration shared by HTTP and WebSocket servers. + # JSON-RPC subsystem configuration shared by HTTP and WebSocket servers. rpc: - # config.rpc.txFeecap limits the fee per gas for RPC-submitted transactions (0 disables the cap). + # -- (int) Maximum fee per gas accepted for transactions submitted via RPC (0 disables the cap). txFeecap: 0 - # config.sync configures chain synchronisation behaviour. + # Chain synchronisation controls. sync: - # config.sync.minPeers defines the minimum peers required before commencing sync. + # -- (int) Minimum number of peers required before synchronisation starts. minPeers: 1 - # config.sync.mode selects the synchronisation mode (e.g., FULL, FAST, SNAP). + # -- (string) Synchronisation mode (FULL, FAST, SNAP, etc.). mode: FULL - # config.remoteConnectionsLimitEnabled toggles limiting simultaneous remote JSON-RPC connections. + # -- (bool) Enable limits on concurrent remote JSON-RPC connections. remoteConnectionsLimitEnabled: false - # config.randomPeerPriorityEnabled shuffles peer priority to avoid fixed ordering bias. + # -- (bool) Randomise peer priority to avoid deterministic ordering. randomPeerPriorityEnabled: true - # config.receiptCompactionEnabled compacts receipt storage to save disk space. + # -- (bool) Enable receipt compaction to reduce disk usage. receiptCompactionEnabled: true - # config.hostAllowlist enumerates hosts authorised to reach the HTTP/WS RPC endpoints. + # -- (list) Hostnames allowed to access the RPC interfaces; `*` permits all. hostAllowlist: - "*" - # config.http configures the HTTP JSON-RPC server. + # HTTP JSON-RPC server configuration. http: - # config.http.enabled toggles the HTTP JSON-RPC listener. + # -- (bool) Enable the HTTP JSON-RPC listener. enabled: true - # config.http.host binds the HTTP JSON-RPC listener interface. + # -- (string) Network interface for the HTTP JSON-RPC listener. host: "0.0.0.0" - # config.http.api lists the JSON-RPC API namespaces enabled over HTTP. + # -- (list) Enabled JSON-RPC API namespaces exposed over HTTP. api: - DEBUG - ETH @@ -157,24 +159,24 @@ config: - PERM - TXPOOL - PLUGINS - # config.http.corsOrigins enumerates allowed CORS origins for HTTP requests. + # -- (list) Allowed CORS origins for HTTP JSON-RPC requests. corsOrigins: - all - # config.http.authenticationEnabled toggles JWT authentication for HTTP requests. + # -- (bool) Enable JWT authentication for HTTP JSON-RPC requests. authenticationEnabled: false - # config.http.maxActiveConnections limits concurrent HTTP connections. + # -- (int) Maximum concurrent HTTP JSON-RPC connections. maxActiveConnections: 2000 - # config.http.maxRequestContentLength caps the HTTP body size in bytes. + # -- (int) Maximum HTTP request body size in bytes. maxRequestContentLength: 524288000 - # config.http.maxBatchSize limits the number of JSON-RPC requests permitted in a single batch. + # -- (int) Maximum number of batched JSON-RPC calls per request. maxBatchSize: 512 - # config.ws configures the WebSocket JSON-RPC server. + # WebSocket JSON-RPC server configuration. ws: - # config.ws.enabled toggles the WebSocket JSON-RPC listener. + # -- (bool) Enable the WebSocket JSON-RPC listener. enabled: true - # config.ws.host binds the WebSocket listener interface. + # -- (string) Network interface for the WebSocket listener. host: "0.0.0.0" - # config.ws.api lists the JSON-RPC API namespaces enabled over WebSockets. + # -- (list) Enabled JSON-RPC API namespaces exposed over WebSockets. api: - DEBUG - ETH @@ -187,28 +189,28 @@ config: - PERM - TXPOOL - PLUGINS - # config.ws.authenticationEnabled toggles JWT authentication for WebSocket requests. + # -- (bool) Enable JWT authentication for WebSocket requests. authenticationEnabled: false - # config.ws.maxActiveConnections limits concurrent WebSocket connections. + # -- (int) Maximum concurrent WebSocket connections. maxActiveConnections: 2000 - # config.ws.maxFrameSize caps the maximum WebSocket frame size in bytes. + # -- (int) Maximum WebSocket frame size in bytes. maxFrameSize: 2097152 - # config.graphql configures the optional GraphQL endpoint. + # GraphQL server configuration. graphql: - # config.graphql.enabled toggles the GraphQL server. + # -- (bool) Enable the GraphQL API server. enabled: true - # config.graphql.host binds the GraphQL server interface. + # -- (string) Network interface for the GraphQL server. host: "0.0.0.0" - # config.graphql.corsOrigins lists allowed origins for GraphQL requests. + # -- (list) Allowed CORS origins for GraphQL requests. corsOrigins: - all - # config.metrics configures the Prometheus metrics subsystem. + # Prometheus metrics export configuration. metrics: - # config.metrics.enabled toggles the Prometheus metrics exporter. + # -- (bool) Enable the Prometheus metrics endpoint. enabled: true - # config.metrics.host binds the metrics endpoint interface. + # -- (string) Network interface for the metrics endpoint. host: "0.0.0.0" - # config.metrics.categories lists the metrics categories exposed. + # -- (list) Metrics categories exposed to Prometheus. categories: - BLOCKCHAIN - ETHEREUM @@ -221,95 +223,101 @@ config: - RPC - SYNCHRONIZER - TRANSACTION_POOL - # config.revertReasonEnabled toggles inclusion of revert reasons in RPC responses. + # -- (bool) Include revert reasons in RPC responses when failures occur. revertReasonEnabled: true -# ingress configures Kubernetes Ingress resources for publicly exposing RPC services. +# Ingress configuration used to expose RPC services via Kubernetes Ingress resources. ingress: - # ingress.enabled toggles creation of an Ingress resource. + # -- (bool) Enable creation of an Ingress resource. enabled: false - # ingress.className sets the ingressClassName field for controller selection. + # -- (string) ingressClassName assigned to the Ingress for controller selection. className: "" - # ingress.annotations attaches metadata annotations for ingress controllers. + # Metadata annotations applied to the Ingress resource. annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" - # ingress.hosts lists host/path routing rules for the Ingress. + # -- (list) Hostname and path routing rules for the Ingress. hosts: - host: chart-example.local paths: - path: / pathType: ImplementationSpecific - # ingress.tls defines TLS secrets and hostnames for HTTPS termination. + # -- (list) TLS configuration for Ingress hosts. tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local -# httpRoute configures Gateway API HTTPRoute resources used with Gateway controllers. +# Gateway API HTTPRoute configuration for clusters with gateway-api controllers. httpRoute: - # httpRoute.enabled toggles rendering of an HTTPRoute resource. + # -- (bool) Enable rendering of an HTTPRoute resource. enabled: false - # httpRoute.annotations applies annotations to the HTTPRoute manifest. + # Metadata annotations applied to the HTTPRoute. annotations: {} - # httpRoute.parentRefs lists Gateway references that should attach this route. + # -- (list) Gateway references that should accept this route. parentRefs: - name: gateway sectionName: http # namespace: default - # httpRoute.hostnames enumerates HTTP hostnames matched by the route. + # -- (list) HTTP hostnames matched by the route. hostnames: - chart-example.local - # httpRoute.rules defines match and filter blocks evaluated by the Gateway. + # -- (list) Rules containing matches and optional filters evaluated by the Gateway. rules: - - matches: - - path: + - + # -- (list) Match conditions evaluated for each request. + matches: + - + # Path matching condition for the request. + path: + # -- (string) Path match type (Exact, PathPrefix, or RegularExpression). type: PathPrefix + # -- (string) Path value used when evaluating the request URL. value: /headers - # filters can be enabled to mutate headers or traffic characteristics. - # - type: RequestHeaderModifier - # requestHeaderModifier: - # set: - # - name: My-Overwrite-Header - # value: this-is-the-only-value - # remove: - # - User-Agent - # Additional matches can be added as needed. - # - matches: - # - path: - # type: PathPrefix - # value: /echo - # headers: - # - name: version - # value: v2 + # filters can be enabled to mutate headers or other request properties. + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # Additional matches can be added as required by traffic patterns. + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 -# openShiftRoute configures an OpenShift Route for clusters running the OpenShift router. +# OpenShift Route configuration for exposing services through the OpenShift router. openShiftRoute: - # openShiftRoute.enabled toggles creation of the Route resource. + # -- (bool) Enable creation of an OpenShift Route resource. enabled: false - # openShiftRoute.annotations attaches metadata annotations for the Route. + # Metadata annotations applied to the Route. annotations: {} - # openShiftRoute.host enforces a specific hostname; leave empty for automatic allocation. + # -- (string) Desired external hostname for the Route; leave empty for automatic assignment. host: "" - # openShiftRoute.path constrains routing to a specific URL path. + # -- (string) URL path prefix handled by the Route. path: "" - # openShiftRoute.wildcardPolicy configures wildcard host handling (None or Subdomain). + # -- (string) Wildcard policy controlling subdomain routing (None or Subdomain). wildcardPolicy: "" - # openShiftRoute.port configures the target service port used by the Route. + # Target service port configuration for the Route. port: - # openShiftRoute.port.targetPort references the named service port exposed via the Route. + # -- (string) Named service port exposed through the Route. targetPort: http - # openShiftRoute.to configures backend weighting for load balancing. + # Primary backend configuration for the Route. to: - # openShiftRoute.to.weight adjusts traffic weighting across multiple service backends. + # -- (int) Backend weight determining traffic distribution. weight: 100 - # openShiftRoute.alternateBackends adds additional backend targets following RouteTargetReference schema. + # -- (list) Additional backend references to balance traffic across services. alternateBackends: [] - # openShiftRoute.tls defines TLS termination settings or disables TLS when set to null. + # -- (object|null) TLS termination settings; set to null to disable TLS. tls: null -# resources defines CPU and memory requests/limits for Besu containers. +# CPU and memory requests or limits for Besu containers. resources: {} # limits: @@ -319,90 +327,92 @@ resources: # cpu: 100m # memory: 128Mi -# livenessProbe configures the HTTP probe used to determine if the container should be restarted. +# Liveness probe configuration ensuring containers restart when unhealthy. livenessProbe: - # livenessProbe.httpGet.path hits the Besu liveness endpoint. + # HTTP GET probe configuration hitting the Besu liveness endpoint. httpGet: + # -- (string) HTTP path used for liveness probing. path: /liveness - # livenessProbe.httpGet.port references the named container port serving HTTP JSON-RPC. + # -- (string|int) Target container port serving the liveness endpoint. port: json-rpc - # livenessProbe.initialDelaySeconds waits before starting liveness checks. + # -- (int) Seconds to wait before starting liveness checks. initialDelaySeconds: 30 - # livenessProbe.periodSeconds schedules how frequently the probe runs. + # -- (int) Frequency of liveness checks in seconds. periodSeconds: 10 - # livenessProbe.timeoutSeconds caps how long the probe waits for a response. + # -- (int) Timeout in seconds before marking the probe as failed. timeoutSeconds: 2 - # livenessProbe.failureThreshold restarts the pod after this many consecutive failures. + # -- (int) Consecutive failures required before the container is restarted. failureThreshold: 3 -# readinessProbe configures the HTTP probe used to advertise readiness to receive traffic. +# Readiness probe configuration signalling when pods can accept traffic. readinessProbe: - # readinessProbe.httpGet.path hits the readiness endpoint with peer and sync thresholds. + # HTTP GET probe configuration hitting the Besu readiness endpoint. httpGet: + # -- (string) HTTP path used for readiness probing, including peer/sync thresholds. path: /readiness?minPeers=0&maxBlocksBehind=100 - # readinessProbe.httpGet.port references the named container port serving HTTP JSON-RPC. + # -- (string|int) Target container port serving the readiness endpoint. port: json-rpc - # readinessProbe.initialDelaySeconds waits before starting readiness checks. + # -- (int) Seconds to wait before starting readiness checks. initialDelaySeconds: 15 - # readinessProbe.periodSeconds schedules how frequently the readiness probe runs. + # -- (int) Frequency of readiness checks in seconds. periodSeconds: 10 - # readinessProbe.timeoutSeconds caps the probe response time. + # -- (int) Timeout in seconds before marking the probe as failed. timeoutSeconds: 2 - # readinessProbe.failureThreshold marks the pod unready after this many consecutive failures. + # -- (int) Consecutive failures required before the pod is considered unready. failureThreshold: 3 -# persistence configures persistent storage for validator and RPC nodes. +# Persistent storage configuration for both validator and RPC StatefulSets. persistence: - # persistence.enabled toggles persistent volume usage for ledger data. + # -- (bool) Enable persistent volume claims for ledger data. enabled: false - # persistence.mountPath is the container directory where the volume is mounted. + # -- (string) Container path where the persistent volume is mounted. mountPath: /data - # persistence.volumeName names the volume when using volumeClaimTemplates. + # -- (string) Logical name for the volume when using volumeClaimTemplates. volumeName: data - # persistence.accessModes declares the desired access modes for the PVC. + # -- (list) Requested access modes for the PersistentVolumeClaim. accessModes: - ReadWriteOnce - # persistence.size defines the requested storage capacity when provisioning PVCs. + # -- (string) Requested storage capacity for each PersistentVolumeClaim. size: 20Gi - # persistence.storageClass selects the Kubernetes StorageClass; leave empty for default. + # -- (string) StorageClass name used for provisioning volumes; empty selects the cluster default. storageClass: "" - # persistence.volumeMode switches between Filesystem and Block volume modes. + # -- (string) Volume mode (Filesystem or Block) requested for the PVC. volumeMode: "" - # persistence.existingClaim references a pre-created PersistentVolumeClaim to reuse. + # -- (string) Name of an existing PersistentVolumeClaim to reuse instead of creating new PVCs. existingClaim: "" - # persistence.annotations attaches metadata annotations to created PVCs. + # Metadata annotations applied to generated PVCs. annotations: {} - # persistence.selector filters PersistentVolumes when binding static PVs. + # PVC selector allowing binding to specific PersistentVolumes. selector: {} - # persistence.retention configures how PVCs are handled during deletion or scale-down. + # Retention policy controlling PVC lifecycle on delete or scale operations. retention: - # persistence.retention.whenDeleted dictates PVC behaviour when the Helm release is deleted. + # -- (string) Behaviour of PVCs when the Helm release is deleted. whenDeleted: Retain - # persistence.retention.whenScaled dictates PVC behaviour when replicas are scaled down. + # -- (string) Behaviour of PVCs when the StatefulSet scales down. whenScaled: Delete - # persistence.subPath mounts a subdirectory of the volume instead of the root. + # -- (string) Subdirectory of the volume to mount instead of the root. subPath: "" - # persistence.readOnly mounts the volume read-only when true. + # -- (bool) Mount the volume read-only when true. readOnly: false -# volumes lists extra volumes attached to each Besu pod for custom configuration or secrets. +# -- (list) Extra volumes attached to Besu pods for custom configuration or secrets. volumes: [] # - name: foo # secret: # secretName: mysecret # optional: false -# volumeMounts lists additional container volume mounts beyond the defaults. +# -- (list) Additional volume mounts applied to Besu containers. volumeMounts: [] # - name: foo # mountPath: "/etc/foo" # readOnly: true -# nodeSelector constrains pod scheduling to nodes with matching labels. +# Node selector constraints influencing where Besu pods can schedule. nodeSelector: {} -# tolerations allows pods to schedule onto nodes with corresponding taints. +# -- (list) Tolerations allowing pods to run on nodes with matching taints. tolerations: [] -# affinity defines node and pod affinity/anti-affinity rules for advanced scheduling. +# Node and pod affinity or anti-affinity rules affecting scheduling decisions. affinity: {} From 4e87a41000d01f7e6a82440d481303d2cea9d409 Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 17:32:30 +0200 Subject: [PATCH 16/17] fix(charts): resolve YAML parsing error in test-connection template - Fixed a YAML parsing error in the `test-connection.yaml` template for network nodes, ensuring proper structure and syntax. - Updated the pod configuration to correctly reference the service port and include necessary annotations for Helm hooks. --- .../templates/tests/test-connection.yaml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 charts/network/charts/network-nodes/templates/tests/test-connection.yaml diff --git a/charts/network/charts/network-nodes/templates/tests/test-connection.yaml b/charts/network/charts/network-nodes/templates/tests/test-connection.yaml deleted file mode 100644 index f7e50e7..0000000 --- a/charts/network/charts/network-nodes/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "nodes.fullname" . }}-test-connection" - labels: - {{- include "nodes.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "nodes.fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never From b8e2837d4cc83459027c7bf133a7b79b51bda6ad Mon Sep 17 00:00:00 2001 From: Roderik van der Veer Date: Wed, 17 Sep 2025 17:37:24 +0200 Subject: [PATCH 17/17] fix(charts): update README and values.yaml for network-bootstrapper configuration - Revised the README.md to enhance clarity and consistency in documenting configuration values for the network-bootstrapper, including detailed descriptions for `clusterDomain`, `defaultStaticNodeDiscoveryPort`, and `fullnameOverride`. - Updated the structure of configuration values in values.yaml to align with best practices, ensuring accurate documentation for users. - Improved overall integrity and readability of the configuration files, addressing YAML parsing errors and enhancing user guidance. --- .../charts/network-bootstrapper/README.md | 6 +-- .../network-bootstrapper/templates/job.yaml | 6 +-- .../charts/network-bootstrapper/values.yaml | 15 +++---- .../charts/network-nodes/templates/NOTES.txt | 30 +++++++++++++ charts/network/templates/NOTES.txt | 45 +++++++++++++++++++ 5 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 charts/network/charts/network-nodes/templates/NOTES.txt diff --git a/charts/network/charts/network-bootstrapper/README.md b/charts/network/charts/network-bootstrapper/README.md index 9a96869..67d1bfe 100644 --- a/charts/network/charts/network-bootstrapper/README.md +++ b/charts/network/charts/network-bootstrapper/README.md @@ -15,9 +15,6 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | | -| clusterDomain | string | `"svc.cluster.local"` | Kubernetes DNS suffix appended to generated static node hostnames when unset by the CLI. | -| defaultStaticNodeDiscoveryPort | int | `30303` | Default UDP discovery port used in generated enode URIs when no override is provided. | -| defaultStaticNodePort | int | `30303` | Default TCP P2P port used in generated enode URIs when no override is provided. | | fullnameOverride | string | `"bootstrapper"` | Override for the fully qualified resource name generated by helpers. | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy controlling when Kubernetes fetches updated image layers. | | image.repository | string | `"ghcr.io/settlemint/network-bootstrapper"` | OCI registry path hosting the network bootstrapper image. | @@ -37,8 +34,11 @@ A Helm chart for Kubernetes | serviceAccount.name | string | `""` | Existing ServiceAccount name to reuse instead of creating one. | | settings.allocations | string | `nil` | Path to a JSON allocations file providing pre-funded accounts. | | settings.chainId | int | `nil` | Explicit chain ID to encode into the genesis file. | +| settings.clusterDomain | string | `"svc.cluster.local"` | Kubernetes DNS suffix appended to generated static node hostnames when unset by the CLI. | | settings.consensus | string | `nil` | Consensus engine to configure (QBFT or IBFTv2). | | settings.contractSizeLimit | int | `nil` | Maximum smart-contract bytecode size accepted by the EVM. | +| settings.defaultStaticNodeDiscoveryPort | int | `30303` | Default UDP discovery port used in generated enode URIs when no override is provided. | +| settings.defaultStaticNodePort | int | `30303` | Default TCP P2P port used in generated enode URIs when no override is provided. | | settings.evmStackSize | int | `nil` | Maximum EVM stack size permitted for contract execution. | | settings.gasLimit | int | `nil` | Genesis block gas limit expressed as a decimal number. | | settings.gasPrice | int | `nil` | Base gas price in wei applied across the network. | diff --git a/charts/network/charts/network-bootstrapper/templates/job.yaml b/charts/network/charts/network-bootstrapper/templates/job.yaml index edf524d..e2bf78b 100644 --- a/charts/network/charts/network-bootstrapper/templates/job.yaml +++ b/charts/network/charts/network-bootstrapper/templates/job.yaml @@ -39,9 +39,9 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} args: - generate - {{- $clusterDomain := default "svc.cluster.local" .Values.clusterDomain }} - {{- $defaultStaticPort := default 30303 .Values.defaultStaticNodePort }} - {{- $defaultStaticDiscovery := default 30303 .Values.defaultStaticNodeDiscoveryPort }} + {{- $clusterDomain := default "svc.cluster.local" .Values.settings.clusterDomain }} + {{- $defaultStaticPort := default 30303 .Values.settings.defaultStaticNodePort }} + {{- $defaultStaticDiscovery := default 30303 .Values.settings.defaultStaticNodeDiscoveryPort }} {{- $resolvedStaticDomain := default $clusterDomain .Values.settings.staticNodeDomain }} {{- $resolvedStaticPort := default $defaultStaticPort .Values.settings.staticNodePort }} {{- $resolvedStaticDiscovery := default $defaultStaticDiscovery .Values.settings.staticNodeDiscoveryPort }} diff --git a/charts/network/charts/network-bootstrapper/values.yaml b/charts/network/charts/network-bootstrapper/values.yaml index b1c46ad..65bd8ff 100644 --- a/charts/network/charts/network-bootstrapper/values.yaml +++ b/charts/network/charts/network-bootstrapper/values.yaml @@ -85,17 +85,14 @@ tolerations: [] # Affinity and anti-affinity rules influencing pod placement. affinity: {} -# -- (string) Kubernetes DNS suffix appended to generated static node hostnames when unset by the CLI. -clusterDomain: svc.cluster.local - -# -- (int) Default TCP P2P port used in generated enode URIs when no override is provided. -defaultStaticNodePort: 30303 - -# -- (int) Default UDP discovery port used in generated enode URIs when no override is provided. -defaultStaticNodeDiscoveryPort: 30303 - # Settings passed to the network-bootstrapper CLI executed by the job. settings: + # -- (string) Kubernetes DNS suffix appended to generated static node hostnames when unset by the CLI. + clusterDomain: svc.cluster.local + # -- (int) Default TCP P2P port used in generated enode URIs when no override is provided. + defaultStaticNodePort: 30303 + # -- (int) Default UDP discovery port used in generated enode URIs when no override is provided. + defaultStaticNodeDiscoveryPort: 30303 # -- (int) Number of validator identities to generate (default 4). validators: # -- (string) DNS suffix appended to generated static node hostnames. diff --git a/charts/network/charts/network-nodes/templates/NOTES.txt b/charts/network/charts/network-nodes/templates/NOTES.txt new file mode 100644 index 0000000..e0fbe58 --- /dev/null +++ b/charts/network/charts/network-nodes/templates/NOTES.txt @@ -0,0 +1,30 @@ +{{- $namespace := .Release.Namespace -}} +{{- $fullname := include "nodes.fullname" . -}} +Thank you for deploying the {{ .Chart.Name }} chart! + +This release creates two StatefulSets (validators and RPC nodes) plus matching Services to expose Besu endpoints. + +Pods and Services +----------------- +List the pods created by this release: + + kubectl -n {{ $namespace }} get pods -l app.kubernetes.io/instance={{ .Release.Name }} + +Validators publish peer-to-peer interfaces via `svc/{{ $fullname }}` while RPC nodes expose JSON-RPC via `svc/{{ $fullname }}-rpc`. + +Accessing RPC Endpoints +----------------------- +Port-forward the RPC service to access JSON-RPC locally: + + kubectl -n {{ $namespace }} port-forward svc/{{ $fullname }}-rpc 8545:{{ .Values.service.ports.rpc }} + +Optional: forward WebSockets or GraphQL by selecting the corresponding service port names (`ws`, `graphql`). + +Observability +------------- +Metrics are available on the `metrics` port of each service. Example scrape using `curl`: + + kubectl -n {{ $namespace }} port-forward svc/{{ $fullname }} 9545:{{ .Values.service.ports.metrics }} + curl -s http://127.0.0.1:9545/metrics + +When finished, cancel any port-forward sessions with Ctrl+C. diff --git a/charts/network/templates/NOTES.txt b/charts/network/templates/NOTES.txt index e69de29..6de4dbd 100644 --- a/charts/network/templates/NOTES.txt +++ b/charts/network/templates/NOTES.txt @@ -0,0 +1,45 @@ +{{- $namespace := .Release.Namespace -}} +{{- $release := .Release.Name -}} +Thanks for installing the {{ .Chart.Name }} chart! + +This umbrella release wires together: +- the `network-bootstrapper` job that generates genesis artefacts and static node definitions, and +- the `network-nodes` StatefulSets that launch Besu validator and RPC nodes. + +Bootstrapper Workflow +--------------------- +1. Wait for the bootstrapper job to finish producing outputs: + + kubectl -n {{ $namespace }} wait --for=condition=complete job -l app.kubernetes.io/name=network-bootstrapper,app.kubernetes.io/instance={{ $release }} + +2. Stream the job logs to review the generated configuration or troubleshoot failures: + + kubectl -n {{ $namespace }} logs job -l app.kubernetes.io/name=network-bootstrapper,app.kubernetes.io/instance={{ $release }} + +3. When `settings.outputType` is `kubernetes`, fetch the generated artefacts: + + kubectl -n {{ $namespace }} get configmaps -l app.kubernetes.io/name=network-bootstrapper,app.kubernetes.io/instance={{ $release }} + kubectl -n {{ $namespace }} get secrets -l app.kubernetes.io/name=network-bootstrapper,app.kubernetes.io/instance={{ $release }} + +Besu Node Operations +-------------------- +1. Confirm the validator and RPC pods are healthy: + + kubectl -n {{ $namespace }} get pods -l app.kubernetes.io/name=besu-node,app.kubernetes.io/instance={{ $release }} + +2. Port-forward the shared validator service (P2P) or the RPC service for local access (Ctrl+C to stop): + + kubectl -n {{ $namespace }} port-forward svc/besu-node 8545:8545 + kubectl -n {{ $namespace }} port-forward svc/besu-node-rpc 8545:8545 + +3. Expose metrics locally for scraping or ad-hoc inspection: + + kubectl -n {{ $namespace }} port-forward svc/besu-node 9545:9545 + +Next Steps +---------- +- Inspect all resources created by this release: + + kubectl -n {{ $namespace }} get all -l app.kubernetes.io/instance={{ $release }} + +- Apply network policies, ingress controllers, or secrets required by your environment before opening the network to external peers.