diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 7b8244b..a0d68e2 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -17,10 +17,11 @@ on: closed, ] pull_request_review: - types: [submitted, dismissed] + types: [ submitted, dismissed ] concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}-${{ github.event.action || 'default' }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref + }}-${{ github.event_name }}-${{ github.event.action || 'default' }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: @@ -88,13 +89,13 @@ jobs: slack_bot_token: ${{ env.SLACK_BOT_TOKEN }} slack_channel_id: ${{ env.SLACK_CHANNEL_ID }} - # Setup dependencies for QA (skip for draft PRs) - - name: Setup dependencies - uses: settlemint/shared-actions/.github/actions/setup-dependencies@e6f1bf8860111910c2ec9538e9edc137f39e8ef1 # main + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - npm_token: ${{ env.NPM_TOKEN }} - disable_node: "true" + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile - name: Login to GitHub Container Registry if: | @@ -213,9 +214,8 @@ jobs: uses: settlemint/shared-actions/.github/actions/build-status-labeler@e6f1bf8860111910c2ec9538e9edc137f39e8ef1 # main with: pr_number: ${{ github.event.pull_request.number }} - workflow_status: - ${{ steps.secret-scan.outcome == 'success' && 'success' || 'failure' - }} + workflow_status: ${{ steps.secret-scan.outcome == 'success' && 'success' || + 'failure' }} # Check PR review status (PR and PR review events only) - name: Check PR review status diff --git a/README.md b/README.md index a78ce6e..d65aa66 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,13 @@ Options: --static-node-namespace Namespace segment inserted between service name and domain for static-nodes entries. --static-node-service-name Headless Service name used when constructing static-nodes hostnames. --static-node-pod-prefix StatefulSet prefix used when constructing validator pod hostnames. + --rpc-node-service-name Headless Service name used when constructing RPC static-nodes hostnames. + --rpc-node-pod-prefix StatefulSet prefix used when constructing RPC pod hostnames. --genesis-configmap-name ConfigMap name that stores the generated genesis.json payload. --static-nodes-configmap-name ConfigMap name that stores the generated static-nodes.json payload. --faucet-artifact-prefix Prefix applied to faucet ConfigMaps and Secrets. -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) --abi-directory Directory containing ABI JSON files to publish as ConfigMaps. --subgraph-hash-file Path to a file containing the subgraph IPFS hash. diff --git a/package.json b/package.json index d2e1d5e..309b4be 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "network-bootstrapper", "module": "src/index.ts", "type": "module", - "version": "1.3.4", + "version": "1.3.5", "private": false, "license": "FSL-1.1-MIT", "author": { diff --git a/src/cli/commands/bootstrap/bootstrap.command.test.ts b/src/cli/commands/bootstrap/bootstrap.command.test.ts index 0528388..ac2c032 100644 --- a/src/cli/commands/bootstrap/bootstrap.command.test.ts +++ b/src/cli/commands/bootstrap/bootstrap.command.test.ts @@ -11,15 +11,20 @@ import type { OutputPayload, OutputType } from "./bootstrap.output.ts"; import { outputResult as realOutputResult } from "./bootstrap.output.ts"; const VALIDATOR_LABEL = "validator nodes"; +const RPC_LABEL = "RPC nodes"; const VALIDATOR_RETURN = 2; +const RPC_RETURN = 2; const GENESIS_MARKER = '"extraData": "0xextra"'; const EXPECTED_DEFAULT_VALIDATOR = 4; +const EXPECTED_DEFAULT_RPC = 2; 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, + rpcNodeServiceName: DEFAULT_RPC_SERVICE_NAME, + rpcNodePodPrefix: DEFAULT_RPC_POD_PREFIX, genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME, staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME, faucetArtifactPrefix: DEFAULT_FAUCET_PREFIX, @@ -35,7 +40,9 @@ const PRIVATE_KEY_REPEAT = 32; const PUBLIC_KEY_REPEAT = 64; const FIRST_VALIDATOR_INDEX = 1; const SECOND_VALIDATOR_INDEX = 2; -const FAUCET_INDEX = VALIDATOR_RETURN + 1; +const FIRST_RPC_FACTORY_INDEX = VALIDATOR_RETURN + 1; +const SECOND_RPC_FACTORY_INDEX = VALIDATOR_RETURN + 2; +const FAUCET_INDEX = VALIDATOR_RETURN + RPC_RETURN + 1; const SAMPLE_SUBGRAPH_HASH = "bafybeigdyrztzd4gufq2bdsd6we3jh7uzulnd2ipkyli5sto6f5j6rlude"; const createFactoryStub = () => { @@ -71,14 +78,15 @@ const expectedPublicKey = (index: number) => { 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, - namespace?: string, - serviceName: string = DEFAULT_SERVICE_NAME, - podPrefix: string = DEFAULT_POD_PREFIX +const buildStaticNodeUri = ( + publicKeyIndex: number, + ordinal: number, + domain: string | undefined, + port: number, + discoveryPort: number, + namespace: string | undefined, + serviceName: string, + podPrefix: string ): string => { const normalizedDomain = domain === undefined || domain.trim().length === 0 @@ -88,7 +96,6 @@ const expectedStaticNodeUri = ( namespace === undefined || namespace.trim().length === 0 ? undefined : namespace.trim(); - const ordinal = index - 1; const podName = `${podPrefix}-${ordinal}`; const segments = [podName, serviceName]; if (normalizedNamespace) { @@ -98,7 +105,7 @@ const expectedStaticNodeUri = ( segments.push(normalizedDomain); } const host = segments.join("."); - const publicKey = expectedPublicKey(index).slice(2); + const publicKey = expectedPublicKey(publicKeyIndex).slice(2); const nodeId = publicKey.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) && publicKey.length === UNCOMPRESSED_PUBLIC_KEY_LENGTH @@ -107,6 +114,47 @@ const expectedStaticNodeUri = ( return `enode://${nodeId}@${host}:${port}?discport=${discoveryPort}`; }; +const expectedStaticNodeUri = ( + index: number, + domain?: string, + port: number = DEFAULT_STATIC_NODE_PORT, + discoveryPort: number = DEFAULT_STATIC_NODE_PORT, + namespace?: string, + serviceName: string = DEFAULT_SERVICE_NAME, + podPrefix: string = DEFAULT_POD_PREFIX +): string => + buildStaticNodeUri( + index, + index - 1, + domain, + port, + discoveryPort, + namespace, + serviceName, + podPrefix + ); + +const expectedRpcStaticNodeUri = ( + publicKeyIndex: number, + ordinal: number, + domain?: string, + port: number = DEFAULT_STATIC_NODE_PORT, + discoveryPort: number = DEFAULT_STATIC_NODE_PORT, + namespace?: string, + serviceName: string = DEFAULT_RPC_SERVICE_NAME, + podPrefix: string = DEFAULT_RPC_POD_PREFIX +): string => + buildStaticNodeUri( + publicKeyIndex, + ordinal, + domain, + port, + discoveryPort, + namespace, + serviceName, + podPrefix + ); + const captureStdout = () => { let captured = ""; const original = process.stdout.write; @@ -220,10 +268,13 @@ describe("CLI command bootstrap", () => { expect(promptCalls).toEqual([ [VALIDATOR_LABEL, undefined, EXPECTED_DEFAULT_VALIDATOR], + [RPC_LABEL, undefined, EXPECTED_DEFAULT_RPC], ]); expect(textPromptCalls).toEqual([ ["Static node service name", DEFAULT_SERVICE_NAME], ["Static node pod prefix", DEFAULT_POD_PREFIX], + ["RPC node service name", DEFAULT_RPC_SERVICE_NAME], + ["RPC node pod prefix", DEFAULT_RPC_POD_PREFIX], ["Genesis ConfigMap name", DEFAULT_GENESIS_CONFIGMAP_NAME], ["Static nodes ConfigMap name", DEFAULT_STATIC_NODES_CONFIGMAP_NAME], ["Faucet artifact prefix", DEFAULT_FAUCET_PREFIX], @@ -231,6 +282,7 @@ describe("CLI command bootstrap", () => { const output = stdout.read(); expect(output).toContain("Genesis"); expect(output).toContain("Validator Nodes"); + expect(output).toContain("RPC Nodes"); expect(output).toContain("Static Nodes"); expect(output).toContain(GENESIS_MARKER); expect(loadAllocationsPath).toBe("/tmp/alloc.json"); @@ -239,11 +291,14 @@ describe("CLI command bootstrap", () => { expect(outputInvocation?.payload.staticNodes).toEqual([ expectedStaticNodeUri(FIRST_VALIDATOR_INDEX), expectedStaticNodeUri(SECOND_VALIDATOR_INDEX), + expectedRpcStaticNodeUri(FIRST_RPC_FACTORY_INDEX, 0), + expectedRpcStaticNodeUri(SECOND_RPC_FACTORY_INDEX, 1), ]); expect(outputInvocation?.payload.abiArtifacts).toEqual([]); expect(outputInvocation?.payload.artifactNames).toEqual({ faucetPrefix: DEFAULT_FAUCET_PREFIX, validatorPrefix: DEFAULT_POD_PREFIX, + rpcPrefix: DEFAULT_RPC_POD_PREFIX, genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME, staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME, subgraphConfigMapName: DEFAULT_SUBGRAPH_CONFIGMAP_NAME, @@ -407,6 +462,8 @@ describe("CLI command bootstrap", () => { "generate", "--validators", "2", + "--rpc-nodes", + "2", "--allocations", "/tmp/mock.json", "--consensus", @@ -489,6 +546,12 @@ describe("CLI command bootstrap", () => { "custom-service", "--static-node-pod-prefix", "custom-validator", + "--rpc-nodes", + "1", + "--rpc-node-service-name", + "custom-rpc-service", + "--rpc-node-pod-prefix", + "custom-rpc", "--genesis-configmap-name", "custom-genesis", "--static-nodes-configmap-name", @@ -509,10 +572,21 @@ describe("CLI command bootstrap", () => { "custom-service", "custom-validator" ), + expectedRpcStaticNodeUri( + 2, + 0, + "svc.cluster.local", + CUSTOM_STATIC_NODE_PORT, + 0, + "network", + "custom-rpc-service", + "custom-rpc" + ), ]); expect(capturedPayload?.artifactNames).toEqual({ faucetPrefix: "custom-faucet", validatorPrefix: "custom-validator", + rpcPrefix: "custom-rpc", genesisConfigMapName: "custom-genesis", staticNodesConfigMapName: "custom-static-nodes", subgraphConfigMapName: DEFAULT_SUBGRAPH_CONFIGMAP_NAME, @@ -556,6 +630,7 @@ describe("CLI command bootstrap", () => { await runBootstrap( { validators: 1, + rpcNodes: 0, staticNodeDomain: "svc.cluster.local", staticNodeNamespace: "network", staticNodePort: CUSTOM_STATIC_NODE_PORT, @@ -624,6 +699,7 @@ describe("CLI command bootstrap", () => { const options: CliOptions = { validators: validatorOverride, + rpcNodes: 0, consensus: ALGORITHM.ibftV2, chainId: 1234, secondsPerBlock: 6, @@ -795,9 +871,11 @@ describe("CLI command bootstrap", () => { loadSubgraphHash: () => Promise.resolve(SAMPLE_SUBGRAPH_HASH), outputResult: (_type, payload) => { expect(payload.validators).toHaveLength(EXPECTED_DEFAULT_VALIDATOR); + expect(payload.rpcNodes).toHaveLength(EXPECTED_DEFAULT_RPC); expect(payload.artifactNames).toEqual({ faucetPrefix: DEFAULT_FAUCET_PREFIX, validatorPrefix: DEFAULT_POD_PREFIX, + rpcPrefix: DEFAULT_RPC_POD_PREFIX, genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME, staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME, subgraphConfigMapName: DEFAULT_SUBGRAPH_CONFIGMAP_NAME, diff --git a/src/cli/commands/bootstrap/bootstrap.command.ts b/src/cli/commands/bootstrap/bootstrap.command.ts index e34dbe5..1462ca4 100644 --- a/src/cli/commands/bootstrap/bootstrap.command.ts +++ b/src/cli/commands/bootstrap/bootstrap.command.ts @@ -43,6 +43,7 @@ type CliOptions = { gasLimit?: string; gasPrice?: number; validators?: number; + rpcNodes?: number; outputType?: OutputType; secondsPerBlock?: number; staticNodeDomain?: string; @@ -51,6 +52,8 @@ type CliOptions = { staticNodeDiscoveryPort?: number; staticNodeServiceName?: string; staticNodePodPrefix?: string; + rpcNodeServiceName?: string; + rpcNodePodPrefix?: string; genesisConfigmapName?: string; staticNodesConfigmapName?: string; faucetArtifactPrefix?: string; @@ -70,10 +73,13 @@ type BootstrapDependencies = { }; const DEFAULT_VALIDATOR_COUNT = 4; +const DEFAULT_RPC_NODE_COUNT = 2; const DEFAULT_STATIC_NODE_PORT = 30_303; const { staticNodeServiceName: DEFAULT_STATIC_NODE_SERVICE_NAME, staticNodePodPrefix: DEFAULT_STATIC_NODE_POD_PREFIX, + rpcNodeServiceName: DEFAULT_RPC_NODE_SERVICE_NAME, + rpcNodePodPrefix: DEFAULT_RPC_NODE_POD_PREFIX, genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME, staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME, faucetArtifactPrefix: DEFAULT_FAUCET_ARTIFACT_PREFIX, @@ -163,6 +169,8 @@ type TextOptionKey = | "staticNodeNamespace" | "staticNodeServiceName" | "staticNodePodPrefix" + | "rpcNodeServiceName" + | "rpcNodePodPrefix" | "genesisConfigmapName" | "staticNodesConfigmapName" | "faucetArtifactPrefix"; @@ -208,6 +216,21 @@ const TEXT_OPTION_DESCRIPTORS: TextOptionDescriptor[] = [ parser: stripSurroundingQuotes, sanitize: (value) => stripSurroundingQuotes(value), }, + { + key: "rpcNodeServiceName", + flag: "--rpc-node-service-name ", + description: + "Headless Service name used when constructing RPC static-nodes hostnames.", + parser: stripSurroundingQuotes, + sanitize: (value) => stripSurroundingQuotes(value), + }, + { + key: "rpcNodePodPrefix", + flag: "--rpc-node-pod-prefix ", + description: "StatefulSet prefix used when constructing RPC pod hostnames.", + parser: stripSurroundingQuotes, + sanitize: (value) => stripSurroundingQuotes(value), + }, { key: "genesisConfigmapName", flag: "--genesis-configmap-name ", @@ -304,12 +327,15 @@ const runBootstrap = async ( outputType, secondsPerBlock, validators: validatorOption, + rpcNodes: rpcNodesOption, staticNodeDomain: staticNodeDomainOption, staticNodeNamespace: staticNodeNamespaceOption, staticNodePort: staticNodePortOption, staticNodeDiscoveryPort: staticNodeDiscoveryPortOption, staticNodeServiceName: staticNodeServiceNameOption, staticNodePodPrefix: staticNodePodPrefixOption, + rpcNodeServiceName: rpcNodeServiceNameOption, + rpcNodePodPrefix: rpcNodePodPrefixOption, genesisConfigmapName: genesisConfigmapNameOption, staticNodesConfigmapName: staticNodesConfigmapNameOption, faucetArtifactPrefix: faucetArtifactPrefixOption, @@ -356,6 +382,12 @@ const runBootstrap = async ( DEFAULT_VALIDATOR_COUNT ); + const rpcNodesCount = await resolveCount( + "RPC nodes", + rpcNodesOption, + DEFAULT_RPC_NODE_COUNT + ); + const staticNodeServiceName = await resolveText( "Static node service name", staticNodeServiceNameOption, @@ -368,6 +400,18 @@ const runBootstrap = async ( DEFAULT_STATIC_NODE_POD_PREFIX ); + const rpcNodeServiceName = await resolveText( + "RPC node service name", + rpcNodeServiceNameOption, + DEFAULT_RPC_NODE_SERVICE_NAME + ); + + const rpcNodePodPrefix = await resolveText( + "RPC node pod prefix", + rpcNodePodPrefixOption, + DEFAULT_RPC_NODE_POD_PREFIX + ); + const genesisConfigMapName = await resolveText( "Genesis ConfigMap name", genesisConfigmapNameOption, @@ -387,8 +431,9 @@ const runBootstrap = async ( ); const validators = generateGroup(deps.factory, validatorsCount); + const rpcNodes = generateGroup(deps.factory, rpcNodesCount); const faucet = deps.factory.generate(); - const staticNodes = createStaticNodeEntries(validators, { + const validatorStaticNodes = createStaticNodeEntries(validators, { namespace: staticNodeNamespaceOption, domain: staticNodeDomainOption, serviceName: staticNodeServiceName, @@ -396,6 +441,15 @@ const runBootstrap = async ( port: staticNodePortOption ?? DEFAULT_STATIC_NODE_PORT, discoveryPort: staticNodeDiscoveryPortOption ?? DEFAULT_STATIC_NODE_PORT, }); + const rpcStaticNodes = createStaticNodeEntries(rpcNodes, { + namespace: staticNodeNamespaceOption, + domain: staticNodeDomainOption, + serviceName: rpcNodeServiceName, + podPrefix: rpcNodePodPrefix, + port: staticNodePortOption ?? DEFAULT_STATIC_NODE_PORT, + discoveryPort: staticNodeDiscoveryPortOption ?? DEFAULT_STATIC_NODE_PORT, + }); + const staticNodes = [...validatorStaticNodes, ...rpcStaticNodes]; const validatorAddresses = validators.map((node) => node.address); @@ -448,10 +502,12 @@ const runBootstrap = async ( faucet, genesis, validators, + rpcNodes, staticNodes, artifactNames: { faucetPrefix: faucetArtifactPrefix, validatorPrefix: staticNodePodPrefix, + rpcPrefix: rpcNodePodPrefix, genesisConfigMapName, staticNodesConfigMapName, subgraphConfigMapName: DEFAULT_SUBGRAPH_CONFIGMAP_NAME, @@ -511,6 +567,12 @@ const createCliCommand = ( createCountParser("Validators"), DEFAULT_VALIDATOR_COUNT ) + .option( + "-r, --rpc-nodes ", + "Number of RPC nodes to generate.", + createCountParser("RPC nodes"), + DEFAULT_RPC_NODE_COUNT + ) .option( "-a, --allocations ", "Path to a genesis allocations JSON file. (default: none)" @@ -618,6 +680,10 @@ const createCliCommand = ( cmd.getOptionValueSource("validators") === "default" ? undefined : options.validators, + rpcNodes: + cmd.getOptionValueSource("rpcNodes") === "default" + ? undefined + : options.rpcNodes, staticNodePort: cmd.getOptionValueSource("staticNodePort") === "default" ? undefined diff --git a/src/cli/commands/bootstrap/bootstrap.output.test.ts b/src/cli/commands/bootstrap/bootstrap.output.test.ts index 5a96603..ebebb2d 100644 --- a/src/cli/commands/bootstrap/bootstrap.output.test.ts +++ b/src/cli/commands/bootstrap/bootstrap.output.test.ts @@ -274,6 +274,7 @@ const samplePayload: OutputPayload = { faucet: sampleFaucet, genesis: sampleGenesis, validators: [sampleValidator], + rpcNodes: [], staticNodes: [ staticNodeUri( sampleValidator, @@ -286,6 +287,7 @@ const samplePayload: OutputPayload = { artifactNames: { faucetPrefix: DEFAULT_FAUCET_PREFIX, validatorPrefix: DEFAULT_POD_PREFIX, + rpcPrefix: ARTIFACT_DEFAULTS.rpcNodePodPrefix, genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME, staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME, subgraphConfigMapName: ARTIFACT_DEFAULTS.subgraphConfigMapName, @@ -332,10 +334,10 @@ describe("outputResult", () => { "besu-faucet-enode", "besu-faucet-private-key", "besu-faucet-pubkey", - "besu-node-validator-0-node-address", - "besu-node-validator-0-enode", - "besu-node-validator-0-private-key", - "besu-node-validator-0-pubkey", + "besu-validators-0-node-address", + "besu-validators-0-enode", + "besu-validators-0-private-key", + "besu-validators-0-pubkey", "abi-sample.json", "besu-genesis.json", "besu-static-nodes.json", @@ -452,7 +454,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-0-node-address"); + expect(mapNames).toContain("besu-validators-0-node-address"); expect(mapNames).toContain("besu-genesis"); expect(mapNames).toContain("besu-faucet-address"); expect(mapNames).toContain("besu-faucet-pubkey"); @@ -466,10 +468,10 @@ describe("outputResult", () => { const secretNames = createdSecrets.map((entry) => entry.name).sort(); expect(secretNames).toEqual([ "besu-faucet-private-key", - "besu-node-validator-0-private-key", + "besu-validators-0-private-key", ]); const privateKeySecret = createdSecrets.find((entry) => - entry.name.endsWith("validator-0-private-key") + entry.name.endsWith("validators-0-private-key") ); expect(privateKeySecret?.data?.privateKey).toMatch(HEX_PREFIX_PATTERN); const staticNodesConfig = createdConfigMaps.find( @@ -554,6 +556,7 @@ describe("outputResult", () => { artifactNames: { faucetPrefix: "custom-faucet", validatorPrefix: "custom-validator", + rpcPrefix: "custom-rpc", genesisConfigMapName: "custom-genesis", staticNodesConfigMapName: "custom-static", subgraphConfigMapName: "custom-subgraph", @@ -636,7 +639,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "ConfigMap besu-node-validator-0-node-address already exists. Delete it or choose a different output target." + "ConfigMap besu-validators-0-node-address already exists. Delete it or choose a different output target." ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -682,7 +685,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Secret besu-node-validator-0-private-key already exists. Delete it or choose a different output target." + "Secret besu-validators-0-private-key already exists. Delete it or choose a different output target." ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -825,7 +828,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-0-node-address: boom" + "Failed to create ConfigMap besu-validators-0-node-address: boom" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -864,7 +867,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-0-node-address: failed" + "Failed to create ConfigMap besu-validators-0-node-address: failed" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -905,7 +908,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-0-node-address: unknown error" + "Failed to create ConfigMap besu-validators-0-node-address: unknown error" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; @@ -946,7 +949,7 @@ describe("outputResult", () => { }) as unknown as ReturnType; await expect(outputResult("kubernetes", samplePayload)).rejects.toThrow( - "Failed to create ConfigMap besu-node-validator-0-node-address: denied" + "Failed to create ConfigMap besu-validators-0-node-address: denied" ); } finally { (KubeConfig.prototype as any).loadFromCluster = originalLoad; diff --git a/src/cli/commands/bootstrap/bootstrap.output.ts b/src/cli/commands/bootstrap/bootstrap.output.ts index dbcd1c4..1e66a31 100644 --- a/src/cli/commands/bootstrap/bootstrap.output.ts +++ b/src/cli/commands/bootstrap/bootstrap.output.ts @@ -31,6 +31,7 @@ type OutputType = "screen" | "file" | "kubernetes"; type ArtifactNames = { faucetPrefix: string; validatorPrefix: string; + rpcPrefix: string; genesisConfigMapName: string; staticNodesConfigMapName: string; subgraphConfigMapName: string; @@ -40,6 +41,7 @@ type OutputPayload = { faucet: GeneratedNodeKey; genesis: BesuGenesis; validators: readonly IndexedNode[]; + rpcNodes: readonly IndexedNode[]; staticNodes: readonly string[]; artifactNames: ArtifactNames; abiArtifacts: readonly AbiArtifact[]; @@ -164,6 +166,7 @@ const outputToScreen = (payload: OutputPayload): void => { } if (artifactFilter.keys) { printGroup("Validator Nodes", payload.validators); + printGroup("RPC Nodes", payload.rpcNodes); printFaucet(payload.faucet); printStaticNodes(payload.staticNodes); } @@ -191,7 +194,10 @@ const outputToFile = async (payload: OutputPayload): Promise => { const { artifactNames, abiArtifacts, artifactFilter } = payload; const validatorSpecs = artifactFilter.keys - ? createValidatorSpecs(payload.validators, artifactNames.validatorPrefix) + ? createNodeArtifactSpecs(payload.validators, artifactNames.validatorPrefix) + : []; + const rpcSpecs = artifactFilter.keys + ? createNodeArtifactSpecs(payload.rpcNodes, artifactNames.rpcPrefix) : []; const faucetConfigSpecs = artifactFilter.keys @@ -229,7 +235,7 @@ const outputToFile = async (payload: OutputPayload): Promise => { } fileEntries.push( - ...[...validatorSpecs, ...faucetSpecs].map((spec) => ({ + ...[...validatorSpecs, ...rpcSpecs, ...faucetSpecs].map((spec) => ({ path: join(directory, spec.name), description: spec.name, contents: `${JSON.stringify({ [spec.key]: spec.value }, null, 2)}\n`, @@ -293,9 +299,12 @@ const outputToKubernetes = async (payload: OutputPayload): Promise => { ? createAllocationConfigSpecs(payload.genesis.alloc, payload.faucet.address) : []; const validatorSpecs = artifactFilter.keys - ? createValidatorSpecs(payload.validators, artifactNames.validatorPrefix) + ? createNodeArtifactSpecs(payload.validators, artifactNames.validatorPrefix) + : []; + const rpcSpecs = artifactFilter.keys + ? createNodeArtifactSpecs(payload.rpcNodes, artifactNames.rpcPrefix) : []; - const allSpecs = [...validatorSpecs]; + const allSpecs = [...validatorSpecs, ...rpcSpecs]; const configMapSpecs: ConfigMapSpec[] = []; if (artifactFilter.keys) { @@ -372,14 +381,14 @@ const outputToKubernetes = async (payload: OutputPayload): Promise => { ); }; -const createValidatorSpecs = ( +const createNodeArtifactSpecs = ( nodes: readonly IndexedNode[], - validatorPrefix: string + prefix: string ): ConfigMapSpec[] => nodes.flatMap((node) => { // Align artifact names with 0-indexed StatefulSet pod ordinals. const ordinal = node.index - 1; - const base = `${validatorPrefix}-${ordinal}`; + const base = `${prefix}-${ordinal}`; return [ { name: `${base}-node-address`, key: "nodeAddress", value: node.address }, { diff --git a/src/cli/commands/bootstrap/bootstrap.selective-artifacts.test.ts b/src/cli/commands/bootstrap/bootstrap.selective-artifacts.test.ts index 21328aa..fd37f78 100644 --- a/src/cli/commands/bootstrap/bootstrap.selective-artifacts.test.ts +++ b/src/cli/commands/bootstrap/bootstrap.selective-artifacts.test.ts @@ -80,10 +80,12 @@ const createSamplePayload = (filter: ArtifactFilter): OutputPayload => ({ enode: "enode://validator1@host:30303", }, ], + rpcNodes: [], staticNodes: ["enode://validator1@host:30303"], artifactNames: { faucetPrefix: "faucet", - validatorPrefix: "besu-node-validator", + validatorPrefix: "besu-validators", + rpcPrefix: "besu-rpc", genesisConfigMapName: "genesis", staticNodesConfigMapName: "static-nodes", subgraphConfigMapName: ARTIFACT_DEFAULTS.subgraphConfigMapName, diff --git a/src/constants/artifact-defaults.ts b/src/constants/artifact-defaults.ts index 37016ec..8243f7c 100644 --- a/src/constants/artifact-defaults.ts +++ b/src/constants/artifact-defaults.ts @@ -1,6 +1,8 @@ const ARTIFACT_DEFAULTS = { - staticNodeServiceName: "besu-node", - staticNodePodPrefix: "besu-node-validator", + staticNodeServiceName: "besu-validators", + staticNodePodPrefix: "besu-validators", + rpcNodeServiceName: "besu-rpc-headless", + rpcNodePodPrefix: "besu-rpc", genesisConfigMapName: "besu-genesis", staticNodesConfigMapName: "besu-static-nodes", faucetArtifactPrefix: "besu-faucet",