Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ct.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ chart-dirs:
- charts
remote: origin
target-branch: main
check-version-increment: false
11 changes: 11 additions & 0 deletions charts/network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ A Helm chart for a blockchain network on Kubernetes
| | network-bootstrapper | * |
| | network-nodes | * |

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| global | object | `{"networkNodes":{"faucetArtifactPrefix":"besu-faucet","genesisConfigMapName":"besu-genesis","podPrefix":"","serviceName":"","staticNodesConfigMapName":"besu-static-nodes"}}` | Global configuration shared across subcharts. |
| global.networkNodes | object | `{"faucetArtifactPrefix":"besu-faucet","genesisConfigMapName":"besu-genesis","podPrefix":"","serviceName":"","staticNodesConfigMapName":"besu-static-nodes"}` | Defaults consumed by Besu network node workloads. |
| global.networkNodes.faucetArtifactPrefix | string | `"besu-faucet"` | Prefix used for faucet ConfigMaps and Secrets. |
| global.networkNodes.genesisConfigMapName | string | `"besu-genesis"` | ConfigMap name storing the generated genesis.json artifact. |
| global.networkNodes.podPrefix | string | `""` | StatefulSet prefix used for validator pod hostnames. |
| global.networkNodes.serviceName | string | `""` | Kubernetes Service name fronting validator pods to align bootstrapper static-nodes output. |
| global.networkNodes.staticNodesConfigMapName | string | `"besu-static-nodes"` | ConfigMap name storing static-nodes.json entries. |
5 changes: 5 additions & 0 deletions charts/network/charts/network-bootstrapper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@ A Helm chart for Kubernetes
| 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.faucetArtifactPrefix | string | `nil` | Prefix used for faucet ConfigMaps and Secrets. |
| 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.genesisConfigMapName | string | `nil` | ConfigMap name storing the generated genesis.json payload. |
| 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.staticNodePodPrefix | string | `nil` | StatefulSet prefix applied to validator pod hostnames. |
| settings.staticNodePort | int | `nil` | TCP P2P port embedded in static node enode URIs. |
| settings.staticNodeServiceName | string | `nil` | Headless Service name used when constructing static node hostnames. |
| settings.staticNodesConfigMapName | string | `nil` | ConfigMap name storing the generated static-nodes.json payload. |
| 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. |
Expand Down
19 changes: 19 additions & 0 deletions charts/network/charts/network-bootstrapper/templates/job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ spec:
{{- $resolvedStaticDomain := default $clusterDomain .Values.settings.staticNodeDomain }}
{{- $resolvedStaticPort := default $defaultStaticPort .Values.settings.staticNodePort }}
{{- $resolvedStaticDiscovery := default $defaultStaticDiscovery .Values.settings.staticNodeDiscoveryPort }}
{{- $globalNodes := default (dict) .Values.global.networkNodes }}
{{- $autoNames := dict "service" "besu-node" "podPrefix" "besu-node-validator" }}
{{- with (index $.Subcharts "network-nodes") }}
{{- $service := include "nodes.fullname" . }}
{{- $_ := set $autoNames "service" $service }}
{{- $_ := set $autoNames "podPrefix" (printf "%s-validator" $service) }}
{{- end }}
{{- $serviceOverride := coalesce .Values.settings.staticNodeServiceName (get $globalNodes "serviceName") }}
{{- $resolvedServiceName := default (index $autoNames "service") $serviceOverride }}
{{- $podOverride := coalesce .Values.settings.staticNodePodPrefix (get $globalNodes "podPrefix") }}
{{- $resolvedPodPrefix := default (index $autoNames "podPrefix") $podOverride }}
{{- $resolvedGenesisName := default "besu-genesis" (default (get $globalNodes "genesisConfigMapName") .Values.settings.genesisConfigMapName) }}
{{- $resolvedStaticNodesName := default "besu-static-nodes" (default (get $globalNodes "staticNodesConfigMapName") .Values.settings.staticNodesConfigMapName) }}
{{- $resolvedFaucetPrefix := default "besu-faucet" (default (get $globalNodes "faucetArtifactPrefix") .Values.settings.faucetArtifactPrefix) }}
{{- with .Values.settings.validators }}
- --validators={{ . }}
{{- end }}
Expand All @@ -54,6 +68,11 @@ spec:
{{- end }}
- --static-node-port={{ $resolvedStaticPort }}
- --static-node-discovery-port={{ $resolvedStaticDiscovery }}
- --static-node-service-name={{ $resolvedServiceName }}
- --static-node-pod-prefix={{ $resolvedPodPrefix }}
- --genesis-configmap-name={{ $resolvedGenesisName }}
- --static-nodes-configmap-name={{ $resolvedStaticNodesName }}
- --faucet-artifact-prefix={{ $resolvedFaucetPrefix }}
{{- with .Values.settings.allocations }}
- --allocations={{ . }}
{{- end }}
Expand Down
10 changes: 10 additions & 0 deletions charts/network/charts/network-bootstrapper/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ settings:
staticNodePort:
# -- (int) UDP discovery port embedded in static node enode URIs.
staticNodeDiscoveryPort:
# -- (string) Headless Service name used when constructing static node hostnames.
staticNodeServiceName:
# -- (string) StatefulSet prefix applied to validator pod hostnames.
staticNodePodPrefix:
# -- (string) ConfigMap name storing the generated genesis.json payload.
genesisConfigMapName:
# -- (string) ConfigMap name storing the generated static-nodes.json payload.
staticNodesConfigMapName:
# -- (string) Prefix used for faucet ConfigMaps and Secrets.
faucetArtifactPrefix:
# -- (string) Path to a JSON allocations file providing pre-funded accounts.
allocations:
# -- (string) Output target for generated artefacts: screen, file, or kubernetes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ metadata:
app.kubernetes.io/component: rpc
spec:
{{- $root := . }}
{{- $globalNodes := .Values.global.networkNodes | default (dict) }}
{{- $genesisConfigMapName := default "besu-genesis" (get $globalNodes "genesisConfigMapName") }}
{{- $staticNodesConfigMapName := default "besu-static-nodes" (get $globalNodes "staticNodesConfigMapName") }}
{{- $persistence := .Values.persistence | default (dict) }}
{{- $persistenceEnabled := default false (get $persistence "enabled") }}
{{- $existingClaim := default "" (get $persistence "existingClaim") }}
Expand Down Expand Up @@ -161,10 +164,10 @@ spec:
name: {{ include "nodes.fullname" . }}-config
- name: besu-genesis
configMap:
name: besu-genesis
name: {{ $genesisConfigMapName }}
- name: besu-static-nodes
configMap:
name: besu-static-nodes
name: {{ $staticNodesConfigMapName }}
{{- if $useExistingClaim }}
- name: {{ $volumeName }}
persistentVolumeClaim:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ spec:
{{- $privateKeyFilename := default "privateKey" .Values.config.privateKeyFilename }}
{{- $shouldMountPersistence := $persistenceEnabled }}
{{- $validatorReplicaBudget := .Values.validatorReplicaCount | int }}
{{- $globalNodes := .Values.global.networkNodes | default (dict) }}
{{- $genesisConfigMapName := default "besu-genesis" (get $globalNodes "genesisConfigMapName") }}
{{- $staticNodesConfigMapName := default "besu-static-nodes" (get $globalNodes "staticNodesConfigMapName") }}
{{- $validatorPodPrefix := default "besu-node-validator" (get $globalNodes "podPrefix") }}
replicas: {{ .Values.validatorReplicaCount }}
serviceName: {{ include "nodes.fullname" . }}
{{- if and $useClaimTemplate (or (ne $whenDeleted "") (ne $whenScaled "")) }}
Expand Down Expand Up @@ -164,17 +168,17 @@ spec:
name: {{ include "nodes.fullname" . }}-config
- name: besu-genesis
configMap:
name: besu-genesis
name: {{ $genesisConfigMapName }}
- name: besu-static-nodes
configMap:
name: besu-static-nodes
name: {{ $staticNodesConfigMapName }}
- name: besu-private-key
projected:
{{- if gt $validatorReplicaBudget 0 }}
sources:
{{- range $i, $_ := until $validatorReplicaBudget }}
- secret:
name: {{ printf "besu-node-validator-%d-private-key" $i }}
name: {{ printf "%s-%d-private-key" $validatorPodPrefix $i }}
items:
- key: privateKey
path: {{ printf "%s-validator-%d/privateKey" (include "nodes.fullname" $root) $i }}
Expand Down
15 changes: 15 additions & 0 deletions charts/network/values.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# This parent chart does not define additional values; override subchart defaults here when packaging composite releases.

# -- (object) Global configuration shared across subcharts.
global:
# -- (object) Defaults consumed by Besu network node workloads.
networkNodes:
# -- (string) Kubernetes Service name fronting validator pods to align bootstrapper static-nodes output.
serviceName: ""
# -- (string) StatefulSet prefix used for validator pod hostnames.
podPrefix: ""
# -- (string) ConfigMap name storing the generated genesis.json artifact.
genesisConfigMapName: besu-genesis
# -- (string) ConfigMap name storing static-nodes.json entries.
staticNodesConfigMapName: besu-static-nodes
# -- (string) Prefix used for faucet ConfigMaps and Secrets.
faucetArtifactPrefix: besu-faucet
69 changes: 65 additions & 4 deletions src/cli/build-command.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";

import { getAddress } from "viem";
import { ARTIFACT_DEFAULTS } from "../constants/artifact-defaults.ts";
import type { BesuAllocAccount } from "../genesis/besu-genesis.service.ts";
import { ALGORITHM } from "../genesis/besu-genesis.service.ts";
import type { GeneratedNodeKey } from "../keys/node-key-factory.ts";
Expand All @@ -16,6 +17,13 @@ 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 {
staticNodeServiceName: DEFAULT_SERVICE_NAME,
staticNodePodPrefix: DEFAULT_POD_PREFIX,
genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME,
staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME,
faucetArtifactPrefix: DEFAULT_FAUCET_PREFIX,
} = ARTIFACT_DEFAULTS;
const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04";
const UNCOMPRESSED_PUBLIC_KEY_LENGTH = 130;
const HEX_RADIX = 16;
Expand Down Expand Up @@ -46,6 +54,10 @@ const createFactoryStub = () => {
};
};

const passthroughTextPrompt: BootstrapDependencies["promptForText"] = ({
defaultValue,
}) => Promise.resolve(defaultValue);

const expectedAddress = (index: number) => {
const pattern = index.toString(HEX_RADIX).padStart(PAD_WIDTH, PAD_CHAR);
return getAddress(`0x${pattern.repeat(ADDRESS_REPEAT)}`);
Expand All @@ -61,7 +73,9 @@ const expectedStaticNodeUri = (
domain?: string,
port: number = DEFAULT_STATIC_NODE_PORT,
discoveryPort: number = DEFAULT_STATIC_NODE_PORT,
namespace?: string
namespace?: string,
serviceName: string = DEFAULT_SERVICE_NAME,
podPrefix: string = DEFAULT_POD_PREFIX
): string => {
const normalizedDomain =
domain === undefined || domain.trim().length === 0
Expand All @@ -72,8 +86,7 @@ const expectedStaticNodeUri = (
? undefined
: namespace.trim();
const ordinal = index - 1;
const podName = `besu-node-validator-${ordinal}`;
const serviceName = "besu-node";
const podName = `${podPrefix}-${ordinal}`;
const segments = [podName, serviceName];
if (normalizedNamespace) {
segments.push(normalizedNamespace);
Expand Down Expand Up @@ -121,6 +134,7 @@ describe("CLI command bootstrap", () => {
test("runBootstrap orchestrates prompts and writes genesis", async () => {
const factory = createFactoryStub();
const promptCalls: [string, number | undefined, number][] = [];
const textPromptCalls: [string, string][] = [];
let loadAllocationsPath: string | undefined;
let outputInvocation:
| {
Expand Down Expand Up @@ -174,6 +188,10 @@ describe("CLI command bootstrap", () => {
genesis: { config: {}, extraData: "0xextra" } as any,
});
},
promptForText: ({ labelText, defaultValue }) => {
textPromptCalls.push([labelText, defaultValue]);
return Promise.resolve(defaultValue);
},
service: {} as any,
loadAllocations: (path: string) => {
loadAllocationsPath = path;
Expand All @@ -194,6 +212,13 @@ describe("CLI command bootstrap", () => {
expect(promptCalls).toEqual([
[VALIDATOR_LABEL, undefined, EXPECTED_DEFAULT_VALIDATOR],
]);
expect(textPromptCalls).toEqual([
["Static node service name", DEFAULT_SERVICE_NAME],
["Static node pod prefix", DEFAULT_POD_PREFIX],
["Genesis ConfigMap name", DEFAULT_GENESIS_CONFIGMAP_NAME],
["Static nodes ConfigMap name", DEFAULT_STATIC_NODES_CONFIGMAP_NAME],
["Faucet artifact prefix", DEFAULT_FAUCET_PREFIX],
]);
const output = stdout.read();
expect(output).toContain("Genesis");
expect(output).toContain("Validator Nodes");
Expand All @@ -205,6 +230,12 @@ describe("CLI command bootstrap", () => {
expectedStaticNodeUri(FIRST_VALIDATOR_INDEX),
expectedStaticNodeUri(SECOND_VALIDATOR_INDEX),
]);
expect(outputInvocation?.payload.artifactNames).toEqual({
faucetPrefix: DEFAULT_FAUCET_PREFIX,
validatorPrefix: DEFAULT_POD_PREFIX,
genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME,
staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME,
});
});

test("createCliCommand wires metadata", () => {
Expand Down Expand Up @@ -243,6 +274,7 @@ describe("CLI command bootstrap", () => {
genesis: { config: {}, extraData: "0xextra" } as any,
});
},
promptForText: passthroughTextPrompt,
service: {} as any,
loadAllocations: () =>
Promise.resolve({} satisfies Record<string, BesuAllocAccount>),
Expand Down Expand Up @@ -309,6 +341,7 @@ describe("CLI command bootstrap", () => {
},
genesis: { config: {}, extraData: "0xextra" } as any,
}),
promptForText: passthroughTextPrompt,
service: {} as any,
loadAllocations: () =>
Promise.resolve({} satisfies Record<string, BesuAllocAccount>),
Expand All @@ -334,6 +367,16 @@ describe("CLI command bootstrap", () => {
"40000",
"--static-node-discovery-port",
"0",
"--static-node-service-name",
"custom-service",
"--static-node-pod-prefix",
"custom-validator",
"--genesis-configmap-name",
"custom-genesis",
"--static-nodes-configmap-name",
"custom-static-nodes",
"--faucet-artifact-prefix",
"custom-faucet",
],
{ from: "node" }
);
Expand All @@ -344,9 +387,17 @@ describe("CLI command bootstrap", () => {
"svc.cluster.local",
CUSTOM_STATIC_NODE_PORT,
0,
"network"
"network",
"custom-service",
"custom-validator"
),
]);
expect(capturedPayload?.artifactNames).toEqual({
faucetPrefix: "custom-faucet",
validatorPrefix: "custom-validator",
genesisConfigMapName: "custom-genesis",
staticNodesConfigMapName: "custom-static-nodes",
});
});

test("runBootstrap builds static nodes with domain and custom ports", async () => {
Expand All @@ -371,6 +422,7 @@ describe("CLI command bootstrap", () => {
genesis: { config: {}, extraData: "0xextra" } as any,
});
},
promptForText: passthroughTextPrompt,
service: {} as any,
loadAllocations: () =>
Promise.resolve({} satisfies Record<string, BesuAllocAccount>),
Expand Down Expand Up @@ -438,6 +490,7 @@ describe("CLI command bootstrap", () => {
genesis: { config: {}, extraData: "0xextra" } as any,
});
},
promptForText: passthroughTextPrompt,
service: {} as any,
loadAllocations: () =>
Promise.resolve({} satisfies Record<string, BesuAllocAccount>),
Expand Down Expand Up @@ -519,6 +572,7 @@ describe("CLI command bootstrap", () => {
},
genesis: { config: {}, extraData: "0x" } as any,
}),
promptForText: passthroughTextPrompt,
service: {} as any,
loadAllocations: () =>
Promise.resolve({} satisfies Record<string, BesuAllocAccount>),
Expand Down Expand Up @@ -602,13 +656,20 @@ describe("CLI command bootstrap", () => {
genesis: { config: {}, extraData: "0xextra" } as any,
});
},
promptForText: passthroughTextPrompt,
service: {} as any,
loadAllocations: () => {
loadAllocationsInvoked = true;
return Promise.resolve({} as Record<string, BesuAllocAccount>);
},
outputResult: (_type, payload) => {
expect(payload.validators).toHaveLength(EXPECTED_DEFAULT_VALIDATOR);
expect(payload.artifactNames).toEqual({
faucetPrefix: DEFAULT_FAUCET_PREFIX,
validatorPrefix: DEFAULT_POD_PREFIX,
genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME,
staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME,
});
return Promise.resolve();
},
};
Expand Down
Loading