From afe3119d35494e30ee14c934f85d19bc729d905a Mon Sep 17 00:00:00 2001 From: Jeromy Cannon Date: Tue, 7 Jan 2025 20:48:45 +0000 Subject: [PATCH] Squashed commit of the following: commit 2207cd4f1d1907b49c13d649fc8ded12d13af672 Author: Jeromy Cannon Date: Tue Jan 7 20:48:04 2025 +0000 fixed issue where key wasn't populated Signed-off-by: Jeromy Cannon commit 754ac73ea045f0b186fe04862670d093fc96df2a Author: Jeromy Cannon Date: Tue Jan 7 20:26:41 2025 +0000 refactored to extract methods for TODO Signed-off-by: Jeromy Cannon commit 214fe28efe18c8f09adb6f50d01eaf23f8bd72f3 Author: Jeromy Cannon Date: Tue Jan 7 20:19:25 2025 +0000 moved generate network json to node setup, updated nodeId to do the -1 inside the template function, add nodeid into network service map Signed-off-by: Jeromy Cannon commit 0766b0f1c64b148b811f80953739fd4bdb9d2af4 Author: Jeromy Cannon Date: Tue Jan 7 18:06:41 2025 +0000 make user specific temp files in case of multi-user machine Signed-off-by: Jeromy Cannon commit aac9ba647d9ba60420283fa20484896f1eb08350 Author: Jeromy Cannon Date: Tue Jan 7 18:00:24 2025 +0000 don't fail task if rm command fails Signed-off-by: Jeromy Cannon commit 26a907e8e824dd0a35e5526fa50319768e289657 Author: Jeromy Cannon Date: Tue Jan 7 17:57:28 2025 +0000 only run solo install once Signed-off-by: Jeromy Cannon commit 28917ca89fba300a80723180dc8810aedf123a6e Author: Jeromy Cannon Date: Tue Jan 7 17:57:00 2025 +0000 set prettierrc print width to 120 Signed-off-by: Jeromy Cannon commit d1c787628e200c6be81f9af0007bf460fab066ed Author: Jeromy Cannon Date: Tue Jan 7 14:27:34 2025 +0000 Squashed commit of the following: commit 3754a273be887682810a7be1e338e7cd2cc8e633 Author: Jeromy Cannon Date: Tue Jan 7 13:01:53 2025 +0000 fixed sleep bug with debugger flag Signed-off-by: Jeromy Cannon commit 9dcbf12d875a976f1f8b51e59b69468ef6acbd7b Author: Jeromy Cannon Date: Tue Jan 7 13:01:38 2025 +0000 add charts dir Signed-off-by: Jeromy Cannon commit 2aa4a14aef45b1971120b3af42d1f7a6679a9b49 Author: Jeromy Cannon Date: Tue Jan 7 13:01:15 2025 +0000 reverted with fixes Signed-off-by: Jeromy Cannon commit 98e747dad9afe8fee790d2e77e01b9315caf0e7e Author: Jeromy Cannon Date: Tue Jan 7 13:01:05 2025 +0000 add node-id to yml files Signed-off-by: Jeromy Cannon commit 268e10679d4b11222514ce0cd7c283e4c524691b Author: Jeromy Cannon Date: Mon Jan 6 14:19:48 2025 +0000 remove unused imports Signed-off-by: Jeromy Cannon commit 93da63a8faf3f71f496f481180a5419659a533ef Author: Jeromy Cannon Date: Fri Jan 3 22:58:39 2025 +0000 update tasks to handle debugger, also updated code for genesis-network.json Signed-off-by: Jeromy Cannon commit c604fd7a9137ab6d5fe6dde7432e737be5191e5d Author: Jeromy Cannon Date: Fri Jan 3 14:44:04 2025 +0000 changes load, but nodes don't start due to incorrect signature Signed-off-by: Jeromy Cannon Signed-off-by: Jeromy Cannon Signed-off-by: Jeromy Cannon --- .prettierrc.json | 3 +- Taskfile.helper.yml | 51 +++++++---- Taskfile.yml | 4 + .../init-containers-values.yaml | 12 ++- .../latitude/init-containers-values.yaml | 12 ++- .../init-containers-values.yaml | 9 +- examples/solo-gke-test/Taskfile.yml | 1 + .../solo-gke-test/init-containers-values.yaml | 7 +- resources/templates/application.properties | 1 + resources/templates/settings.txt | 16 +--- src/commands/flags.ts | 12 +++ src/commands/network.ts | 24 ++--- src/commands/node/tasks.ts | 91 +++++++++++++------ src/core/account_manager.ts | 10 +- .../genesis_network_data_constructor.ts | 68 ++++++++++---- .../genesis_network_data_wrapper.ts | 34 +++++++ .../genesis_network_node_data_wrapper.ts | 43 ++++----- ...nesis_network_roster_entry_data_wrapper.ts | 37 ++++++++ src/core/helpers.ts | 2 +- src/core/key_manager.ts | 13 +++ src/core/network_node_services.ts | 16 ++++ src/core/platform_installer.ts | 19 +++- src/core/profile_manager.ts | 45 ++------- src/core/templates.ts | 2 +- src/types/index.ts | 15 ++- 25 files changed, 371 insertions(+), 176 deletions(-) create mode 100644 src/core/genesis_network_models/genesis_network_data_wrapper.ts create mode 100644 src/core/genesis_network_models/genesis_network_roster_entry_data_wrapper.ts diff --git a/.prettierrc.json b/.prettierrc.json index 2399e7ec7..677231d93 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,5 +2,6 @@ "bracketSpacing": false, "singleQuote": true, "trailingComma": "all", - "arrowParens": "avoid" + "arrowParens": "avoid", + "printWidth": 120 } diff --git a/Taskfile.helper.yml b/Taskfile.helper.yml index 2e6f9760e..b358f6f84 100644 --- a/Taskfile.helper.yml +++ b/Taskfile.helper.yml @@ -14,15 +14,13 @@ vars: solo_keys_dir: "{{ .solo_cache_dir }}/keys" solo_bin_dir: "{{ .solo_user_dir }}/bin" run_build_file: - sh: (echo "/tmp/run-build-$(date +%Y%m%d%H%M%S)") + sh: (echo "/tmp/${USER}-run-build-$(date +%Y%m%d%H%M%S)") var_check_file: - sh: (echo "/tmp/var-check-$(date +%Y%m%d%H%M%S)") + sh: (echo "/tmp/${USER}-var-check-$(date +%Y%m%d%H%M%S)") minio_flag_file: - sh: (echo "/tmp/minio-flag-$(date +%Y%m%d%H%M%S)") - solo_chart_file: - sh: (echo "/tmp/solo-chart-$(date +%Y%m%d%H%M%S)") - solo_consensus_file: - sh: (echo "/tmp/solo-consensus-$(date +%Y%m%d%H%M%S)") + sh: (echo "/tmp/${USER}-minio-flag-$(date +%Y%m%d%H%M%S)") + solo_install_file: + sh: (echo "/tmp/${USER}-solo-install-$(date +%Y%m%d%H%M%S)") env: SOLO_CLUSTER_SETUP_NAMESPACE: solo-setup @@ -75,6 +73,8 @@ tasks: - echo "LOG4J2_FLAG=${LOG4J2_FLAG}" - echo "APPLICATION_PROPERTIES_FLAG=${APPLICATION_PROPERTIES_FLAG}" - echo "LOCAL_BUILD_FLAG=${LOCAL_BUILD_FLAG}" + - echo "DEBUG_NODE_ALIAS=${DEBUG_NODE_ALIAS}" + - echo "SOLO_CHARTS_DIR_FLAG=${SOLO_CHARTS_DIR_FLAG}" - touch {{ .var_check_file }} readme: @@ -92,10 +92,18 @@ tasks: - echo "Use command 'task default-with-relay' to deploy the network with a relay node." install:solo: + silent: true internal: true + status: + - test -f {{ .solo_install_file }} cmds: - - cd .. + - | + if [[ "$(ls -1 package.json)" == "" ]]; then + cd .. + fi + pwd - npm install + - touch {{ .solo_install_file }} install:kubectl:darwin: internal: true @@ -157,14 +165,16 @@ tasks: - task: "init" cmds: - | - unset RELEASE_FLAG - if [[ "${LOCAL_BUILD_FLAG}" == "" ]]; then - export RELEASE_FLAG='--release-tag {{.CONSENSUS_NODE_VERSION}}' + if [[ "${DEBUG_NODE_ALIAS}" != "" ]]; then + export DEBUG_NODE_FLAG="--debug-node-alias {{ .DEBUG_NODE_ALIAS }}" + fi + if [[ "${CONSENSUS_NODE_VERSION}" != "" ]]; then + export CONSENSUS_NODE_FLAG='--release-tag {{.CONSENSUS_NODE_VERSION}}' fi if [[ "${SOLO_CHART_VERSION}" != "" ]]; then export SOLO_CHART_FLAG='--solo-chart-version ${SOLO_CHART_VERSION}' fi - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- network deploy --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} ${RELEASE_FLAG} ${SOLO_CHART_FLAG} ${VALUES_FLAG} ${SETTINGS_FLAG} ${LOG4J2_FLAG} ${APPLICATION_PROPERTIES_FLAG} ${GENESIS_THROTTLES_FLAG} -q + SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- network deploy --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} ${CONSENSUS_NODE_FLAG} ${SOLO_CHART_FLAG} ${VALUES_FLAG} ${SETTINGS_FLAG} ${LOG4J2_FLAG} ${APPLICATION_PROPERTIES_FLAG} ${GENESIS_THROTTLES_FLAG} ${DEBUG_NODE_FLAG} ${SOLO_CHARTS_DIR_FLAG} -q - | if [[ "${CONSENSUS_NODE_VERSION}" != "" ]]; then export CONSENSUS_NODE_FLAG='--release-tag ${CONSENSUS_NODE_VERSION}' @@ -183,7 +193,11 @@ tasks: deps: - task: "init" cmds: - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} -q {{ .CLI_ARGS }} + - | + if [[ "${DEBUG_NODE_ALIAS}" != "" ]]; then + export DEBUG_NODE_FLAG="--debug-node-alias {{ .DEBUG_NODE_ALIAS }}" + fi + SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} ${DEBUG_NODE_FLAG} -q {{ .CLI_ARGS }} - | if [[ "{{ .use_port_forwards }}" == "true" ]];then echo "Enable port forwarding for Hedera Node" @@ -259,7 +273,7 @@ tasks: deps: - task: "init" cmds: - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- cluster setup --cluster-setup-namespace "${SOLO_CLUSTER_SETUP_NAMESPACE}" -q + - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- cluster setup --cluster-setup-namespace "${SOLO_CLUSTER_SETUP_NAMESPACE}" ${SOLO_CHARTS_DIR_FLAG} -q cluster:destroy: cmds: @@ -409,8 +423,7 @@ tasks: silent: true cmds: - echo "Cleaning up temporary files..." - - rm -f /tmp/run-build-* - - rm -f /tmp/var-check-* - - rm -f /tmp/minio-flag-* - - rm -f /tmp/solo-chart-* - - rm -f /tmp/solo-consensus-* + - rm -f /tmp/${USER}-run-build-* || true + - rm -f /tmp/${USER}-var-check-* || true + - rm -f /tmp/${USER}-minio-flag-* || true + - rm -f /tmp/${USER}-solo-install-* || true diff --git a/Taskfile.yml b/Taskfile.yml index 5994169b9..f6e15388f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -8,6 +8,10 @@ env: SOLO_NAMESPACE: solo-e2e # SOLO_CHART_VERSION: 0.39.0 # CONSENSUS_NODE_VERSION: v0.58.0 + HEDERA_SERVICES_ROOT: "/Users/user/source/hedera-services" + # LOCAL_BUILD_FLAG: "--local-build-path {{.HEDERA_SERVICES_ROOT}}/hedera-node/data" + # DEBUG_NODE_ALIAS: "node2" + # SOLO_CHARTS_DIR_FLAG: "-d /Users/user/source/solo-charts/charts" vars: use_port_forwards: "true" diff --git a/examples/custom-network-config/init-containers-values.yaml b/examples/custom-network-config/init-containers-values.yaml index 657aa388c..a5b553376 100644 --- a/examples/custom-network-config/init-containers-values.yaml +++ b/examples/custom-network-config/init-containers-values.yaml @@ -9,6 +9,7 @@ hedera: mountPath: /data-saved nodes: - name: node1 + nodeId: 0 accountId: 0.0.3 root: resources: @@ -19,6 +20,7 @@ hedera: cpu: 24 memory: 256Gi - name: node2 + nodeId: 1 accountId: 0.0.4 root: resources: @@ -29,6 +31,7 @@ hedera: cpu: 24 memory: 256Gi - name: node3 + nodeId: 2 accountId: 0.0.5 root: resources: @@ -39,6 +42,7 @@ hedera: cpu: 24 memory: 256Gi - name: node4 + nodeId: 3 accountId: 0.0.6 root: resources: @@ -49,6 +53,7 @@ hedera: cpu: 24 memory: 256Gi - name: node5 + nodeId: 4 accountId: 0.0.7 root: resources: @@ -59,6 +64,7 @@ hedera: cpu: 24 memory: 256Gi - name: node6 + nodeId: 5 accountId: 0.0.8 root: resources: @@ -69,6 +75,7 @@ hedera: cpu: 24 memory: 256Gi - name: node7 + nodeId: 6 accountId: 0.0.9 root: resources: @@ -79,6 +86,7 @@ hedera: cpu: 24 memory: 256Gi - name: node8 + nodeId: 7 accountId: 0.0.10 root: resources: @@ -89,6 +97,7 @@ hedera: cpu: 24 memory: 256Gi - name: node9 + nodeId: 8 accountId: 0.0.11 root: resources: @@ -99,6 +108,7 @@ hedera: cpu: 24 memory: 256Gi - name: node10 + nodeId: 9 accountId: 0.0.12 root: resources: @@ -109,8 +119,6 @@ hedera: cpu: 24 memory: 256Gi defaults: - envoyProxy: - loadBalancerEnabled: true sidecars: recordStreamUploader: resources: diff --git a/examples/performance-tuning/latitude/init-containers-values.yaml b/examples/performance-tuning/latitude/init-containers-values.yaml index 55713bd70..12888072d 100644 --- a/examples/performance-tuning/latitude/init-containers-values.yaml +++ b/examples/performance-tuning/latitude/init-containers-values.yaml @@ -9,6 +9,7 @@ hedera: mountPath: /data-saved nodes: - name: node1 + nodeId: 0 accountId: 0.0.3 root: resources: @@ -19,6 +20,7 @@ hedera: cpu: 24 memory: 256Gi - name: node2 + nodeId: 1 accountId: 0.0.4 root: resources: @@ -29,6 +31,7 @@ hedera: cpu: 24 memory: 256Gi - name: node3 + nodeId: 2 accountId: 0.0.5 root: resources: @@ -39,6 +42,7 @@ hedera: cpu: 24 memory: 256Gi - name: node4 + nodeId: 3 accountId: 0.0.6 root: resources: @@ -49,6 +53,7 @@ hedera: cpu: 24 memory: 256Gi - name: node5 + nodeId: 4 accountId: 0.0.7 root: resources: @@ -59,6 +64,7 @@ hedera: cpu: 24 memory: 256Gi - name: node6 + nodeId: 5 accountId: 0.0.8 root: resources: @@ -69,6 +75,7 @@ hedera: cpu: 24 memory: 256Gi - name: node7 + nodeId: 6 accountId: 0.0.9 root: resources: @@ -79,6 +86,7 @@ hedera: cpu: 24 memory: 256Gi - name: node8 + nodeId: 7 accountId: 0.0.10 root: resources: @@ -89,6 +97,7 @@ hedera: cpu: 24 memory: 256Gi - name: node9 + nodeId: 8 accountId: 0.0.11 root: resources: @@ -99,6 +108,7 @@ hedera: cpu: 24 memory: 256Gi - name: node10 + nodeId: 9 accountId: 0.0.12 root: resources: @@ -111,8 +121,6 @@ hedera: defaults: haproxy: serviceType: NodePort - envoyProxy: - loadBalancerEnabled: true sidecars: recordStreamUploader: resources: diff --git a/examples/performance-tuning/solo-perf-test/init-containers-values.yaml b/examples/performance-tuning/solo-perf-test/init-containers-values.yaml index 1ed08a4ca..98846be9e 100644 --- a/examples/performance-tuning/solo-perf-test/init-containers-values.yaml +++ b/examples/performance-tuning/solo-perf-test/init-containers-values.yaml @@ -9,6 +9,7 @@ hedera: mountPath: /data-saved nodes: - name: node0 + nodeId: 0 accountId: 0.0.3 root: resources: @@ -19,6 +20,7 @@ hedera: cpu: 4 memory: 31Gi - name: node1 + nodeId: 1 accountId: 0.0.4 root: resources: @@ -29,6 +31,7 @@ hedera: cpu: 4 memory: 31Gi - name: node2 + nodeId: 2 accountId: 0.0.5 root: resources: @@ -39,6 +42,7 @@ hedera: cpu: 4 memory: 31Gi - name: node3 + nodeId: 3 accountId: 0.0.6 root: resources: @@ -49,6 +53,7 @@ hedera: cpu: 4 memory: 31Gi - name: node4 + nodeId: 4 accountId: 0.0.7 root: resources: @@ -59,6 +64,7 @@ hedera: cpu: 4 memory: 31Gi - name: node5 + nodeId: 5 accountId: 0.0.8 root: resources: @@ -69,6 +75,7 @@ hedera: cpu: 4 memory: 31Gi - name: node6 + nodeId: 6 accountId: 0.0.9 root: resources: @@ -79,8 +86,6 @@ hedera: cpu: 4 memory: 31Gi defaults: - envoyProxy: - loadBalancerEnabled: true sidecars: recordStreamUploader: resources: diff --git a/examples/solo-gke-test/Taskfile.yml b/examples/solo-gke-test/Taskfile.yml index e5899e35e..daeaeebd2 100644 --- a/examples/solo-gke-test/Taskfile.yml +++ b/examples/solo-gke-test/Taskfile.yml @@ -18,3 +18,4 @@ env: HEDERA_SERVICES_ROOT: "/Users/user/source/hedera-services" LOCAL_BUILD_FLAG: "--local-build-path {{.HEDERA_SERVICES_ROOT}}/hedera-node/data" GENESIS_THROTTLES_FLAG: "--genesis-throttles-file {{.USER_WORKING_DIR}}/throttles.json" + # SOLO_CHARTS_DIR_FLAG: "-d /Users/user/source/solo-charts/charts" diff --git a/examples/solo-gke-test/init-containers-values.yaml b/examples/solo-gke-test/init-containers-values.yaml index 76e8437a8..f69c67714 100644 --- a/examples/solo-gke-test/init-containers-values.yaml +++ b/examples/solo-gke-test/init-containers-values.yaml @@ -9,6 +9,7 @@ hedera: mountPath: /data-saved nodes: - name: node1 + nodeId: 0 accountId: 0.0.3 root: resources: @@ -19,6 +20,7 @@ hedera: cpu: 4 memory: 31Gi - name: node2 + nodeId: 1 accountId: 0.0.4 root: resources: @@ -29,6 +31,7 @@ hedera: cpu: 4 memory: 31Gi - name: node3 + nodeId: 2 accountId: 0.0.5 root: resources: @@ -39,6 +42,7 @@ hedera: cpu: 4 memory: 31Gi - name: node4 + nodeId: 3 accountId: 0.0.6 root: resources: @@ -49,6 +53,7 @@ hedera: cpu: 4 memory: 31Gi - name: node5 + nodeId: 4 accountId: 0.0.7 root: resources: @@ -59,8 +64,6 @@ hedera: cpu: 4 memory: 31Gi defaults: - envoyProxy: - loadBalancerEnabled: true sidecars: recordStreamUploader: resources: diff --git a/resources/templates/application.properties b/resources/templates/application.properties index aa70f28fb..237aec60f 100644 --- a/resources/templates/application.properties +++ b/resources/templates/application.properties @@ -16,3 +16,4 @@ staking.periodMins=1 nodes.updateAccountIdAllowed=true # TODO: remove once we have the uploader enabled, required for current p0 deadline blockStream.streamMode=RECORDS +addressBook.useRosterLifecycle=true diff --git a/resources/templates/settings.txt b/resources/templates/settings.txt index 94d252e95..c35797abc 100644 --- a/resources/templates/settings.txt +++ b/resources/templates/settings.txt @@ -1,16 +1,2 @@ -checkSignedStateFromDisk, 1 -csvFileName, MainNetStats -doUpnp, false -loadKeysFromPfxFiles, 0 -maxOutgoingSyncs, 1 -reconnect.active, 1 -reconnect.reconnectWindowSeconds, -1 -showInternalStats, 1 -state.saveStatePeriod, 900 -useLoopbackIp, false -waitAtStartup, false state.mainClassNameOverride, com.hedera.services.ServicesMain -maxEventQueueForCons, 1000 -merkleDb.hashesRamToDiskThreshold, 8388608 -event.creation.maxCreationRate, 20 -virtualMap.familyThrottleThreshold, 6000000000 +state.saveStatePeriod, 60 diff --git a/src/commands/flags.ts b/src/commands/flags.ts index 3c57fedad..e5f54b9a7 100644 --- a/src/commands/flags.ts +++ b/src/commands/flags.ts @@ -1675,6 +1675,17 @@ export class Flags { prompt: undefined, }; + static readonly loadBalancerEnabled: CommandFlag = { + constName: 'loadBalancerEnabled', + name: 'load-balancer', + definition: { + describe: 'Enable load balancer for network node proxies', + defaultValue: false, + type: 'boolean', + }, + prompt: undefined, + }; + static readonly allFlags: CommandFlag[] = [ Flags.accountId, Flags.amount, @@ -1727,6 +1738,7 @@ export class Flags { Flags.hederaExplorerTlsLoadBalancerIp, Flags.hederaExplorerVersion, Flags.inputDir, + Flags.loadBalancerEnabled, Flags.localBuildPath, Flags.log4j2Xml, Flags.mirrorNodeVersion, diff --git a/src/commands/network.ts b/src/commands/network.ts index 8488a930a..3baec9389 100644 --- a/src/commands/network.ts +++ b/src/commands/network.ts @@ -38,7 +38,6 @@ import {ConsensusNodeComponent} from '../core/config/remote/components/consensus import {ConsensusNodeStates} from '../core/config/remote/enumerations.js'; import {EnvoyProxyComponent} from '../core/config/remote/components/envoy_proxy_component.js'; import {HaProxyComponent} from '../core/config/remote/components/ha_proxy_component.js'; -import {GenesisNetworkDataConstructor} from '../core/genesis_network_models/genesis_network_data_constructor.js'; import {v4 as uuidv4} from 'uuid'; import * as Base64 from 'js-base64'; @@ -47,6 +46,7 @@ export interface NetworkDeployConfigClass { cacheDir: string; chartDirectory: string; enablePrometheusSvcMonitor: boolean; + loadBalancerEnabled: boolean; soloChartVersion: string; namespace: string; nodeAliasesUnparsed: string; @@ -64,7 +64,6 @@ export interface NetworkDeployConfigClass { grpcWebTlsCertificatePath: string; grpcTlsKeyPath: string; grpcWebTlsKeyPath: string; - genesisNetworkData: GenesisNetworkDataConstructor; genesisThrottlesFile: string; resolvedThrottlesFile: string; getUnusedConfigs: () => string[]; @@ -123,6 +122,7 @@ export class NetworkCommand extends BaseCommand { flags.enablePrometheusSvcMonitor, flags.soloChartVersion, flags.debugNodeAlias, + flags.loadBalancerEnabled, flags.log4j2Xml, flags.namespace, flags.nodeAliasesUnparsed, @@ -225,13 +225,13 @@ export class NetworkCommand extends BaseCommand { valuesFile?: string; haproxyIpsParsed?: Record; envoyIpsParsed?: Record; - genesisNetworkData: GenesisNetworkDataConstructor; storageType: constants.StorageType; resolvedThrottlesFile: string; storageAccessKey: string; storageSecrets: string; storageEndpoint: string; storageBucket: string; + loadBalancerEnabled: boolean; }) { let valuesArg = config.chartDirectory ? `-f ${path.join(config.chartDirectory, 'solo-deployment', 'values.yaml')}` @@ -280,15 +280,11 @@ export class NetworkCommand extends BaseCommand { valuesArg += ` --set minio-server.tenant.buckets[0].name=${config.storageBucket}`; } const profileName = this.configManager.getFlag(flags.profileName) as string; - this.profileValuesFile = await this.profileManager.prepareValuesForSoloChart( - profileName, - config.genesisNetworkData, - ); + this.profileValuesFile = await this.profileManager.prepareValuesForSoloChart(profileName); if (this.profileValuesFile) { valuesArg += this.prepareValuesFiles(this.profileValuesFile); } - // do not deploy mirror node until after we have the updated address book valuesArg += ` --set "telemetry.prometheus.svcMonitor.enabled=${config.enablePrometheusSvcMonitor}"`; valuesArg += ` --set "defaults.volumeClaims.enabled=${config.persistentVolumeClaims}"`; @@ -315,6 +311,11 @@ export class NetworkCommand extends BaseCommand { valuesArg += ` --set-file "hedera.configMaps.genesisThrottlesJson=${config.resolvedThrottlesFile}"`; } + if (config.loadBalancerEnabled) { + valuesArg += ' --set "defaults.haproxy.serviceType=LoadBalancer"'; + valuesArg += ' --set "defaults.envoyProxy.serviceType=LoadBalancer"'; + } + if (config.valuesFile) { valuesArg += this.prepareValuesFiles(config.valuesFile); } @@ -339,6 +340,7 @@ export class NetworkCommand extends BaseCommand { flags.chainId, flags.chartDirectory, flags.debugNodeAlias, + flags.loadBalancerEnabled, flags.log4j2Xml, flags.persistentVolumeClaims, flags.profileName, @@ -392,12 +394,6 @@ export class NetworkCommand extends BaseCommand { config.stagingDir = Templates.renderStagingDir(config.cacheDir, config.releaseTag); config.stagingKeysDir = path.join(validatePath(config.stagingDir), 'keys'); - config.genesisNetworkData = await GenesisNetworkDataConstructor.initialize( - config.nodeAliases, - this.keyManager, - config.keysDir, - ); - config.resolvedThrottlesFile = resolveValidJsonFilePath( config.genesisThrottlesFile, flags.genesisThrottlesFile.definition.defaultValue as string, diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index 482dbca28..07e755ee3 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -24,8 +24,6 @@ import {type ChartManager} from '../../core/chart_manager.js'; import {type CertificateManager} from '../../core/certificate_manager.js'; import {Zippy} from '../../core/zippy.js'; import * as constants from '../../core/constants.js'; -import {Templates} from '../../core/templates.js'; -import {Task} from '../../core/task.js'; import { DEFAULT_NETWORK_NODE_NAME, FREEZE_ADMIN_ACCOUNT, @@ -33,6 +31,8 @@ import { IGNORED_NODE_ACCOUNT_ID, TREASURY_ACCOUNT_ID, } from '../../core/constants.js'; +import {Templates} from '../../core/templates.js'; +import {Task} from '../../core/task.js'; import { AccountBalanceQuery, AccountId, @@ -42,10 +42,10 @@ import { FreezeTransaction, FreezeType, Long, - PrivateKey, NodeCreateTransaction, NodeDeleteTransaction, NodeUpdateTransaction, + PrivateKey, Timestamp, } from '@hashgraph/sdk'; import {IllegalArgumentError, MissingArgumentError, SoloError} from '../../core/errors.js'; @@ -68,18 +68,17 @@ import { type ConfigBuilder, type NodeAlias, type NodeAliases, - type NodeId, type PodName, type SkipCheck, } from '../../types/aliases.js'; import {NodeStatusCodes, NodeStatusEnums, NodeSubcommandType} from '../../core/enumerations.js'; -import * as x509 from '@peculiar/x509'; import type {NodeDeleteConfigClass, NodeRefreshConfigClass, NodeUpdateConfigClass} from './configs.js'; import {type Lease} from '../../core/lease/lease.js'; import {ListrLease} from '../../core/lease/listr_lease.js'; import {Duration} from '../../core/time/duration.js'; import {type BaseCommand} from '../base.js'; import {type NodeAddConfigClass} from './node_add_config.js'; +import {GenesisNetworkDataConstructor} from '../../core/genesis_network_models/genesis_network_data_constructor.js'; export class NodeCommandTasks { private readonly accountManager: AccountManager; @@ -305,16 +304,21 @@ export class NodeCommandTasks { config: {namespace}, } = ctx; + const enableDebugger = ctx.config.debugNodeAlias && status !== NodeStatusCodes.FREEZE_COMPLETE; + const subTasks = nodeAliases.map((nodeAlias, i) => { const reminder = 'debugNodeAlias' in ctx.config && ctx.config.debugNodeAlias === nodeAlias && status !== NodeStatusCodes.FREEZE_COMPLETE - ? 'Please attach JVM debugger now.' + ? 'Please attach JVM debugger now. Sleeping for 1 hour, hit ctrl-c once debugging is complete.' : ''; const title = `Check network pod: ${chalk.yellow(nodeAlias)} ${chalk.red(reminder)}`; const subTask = async (ctx: any, task: ListrTaskWrapper) => { + if (enableDebugger) { + await sleep(Duration.ofHours(1)); + } ctx.config.podNames[nodeAlias] = await this._checkNetworkNodeActiveness( namespace, nodeAlias, @@ -511,15 +515,6 @@ export class NodeCommandTasks { ); } - _loadPermCertificate(certFullPath: string) { - const certPem = fs.readFileSync(certFullPath).toString(); - const decodedDers = x509.PemConverter.decode(certPem); - if (!decodedDers || decodedDers.length === 0) { - throw new SoloError('unable to load perm key: ' + certFullPath); - } - return new Uint8Array(decodedDers[0]); - } - async _addStake( namespace: string, accountId: string, @@ -547,7 +542,7 @@ export class NodeCommandTasks { // Create the transaction const transaction = new AccountUpdateTransaction() .setAccountId(accountId) - .setStakedNodeId(Templates.nodeIdFromNodeAlias(nodeAlias) - 1) + .setStakedNodeId(Templates.nodeIdFromNodeAlias(nodeAlias)) .freezeWith(client); // Sign the transaction with the account's private key @@ -876,13 +871,20 @@ export class NodeCommandTasks { } setupNetworkNodes(nodeAliasesProperty: string) { - return new Task('Setup network nodes', (ctx: any, task: ListrTaskWrapper) => { + return new Task('Setup network nodes', async (ctx: any, task: ListrTaskWrapper) => { + await this.generateGenesisNetworkJson( + ctx.config.namespace, + ctx.config.nodeAliases, + ctx.config.keysDir, + ctx.config.stagingDir, + ); + const subTasks = []; for (const nodeAlias of ctx.config[nodeAliasesProperty]) { const podName = ctx.config.podNames[nodeAlias]; subTasks.push({ title: `Node: ${chalk.yellow(nodeAlias)}`, - task: () => this.platformInstaller.taskSetup(podName), + task: () => this.platformInstaller.taskSetup(podName, ctx.config.stagingDir), }); } @@ -894,6 +896,33 @@ export class NodeCommandTasks { }); } + /** + * Generate genesis network json file + * @private + * @param namespace - namespace + * @param nodeAliases - node aliases + * @param keysDir - keys directory + * @param stagingDir - staging directory + */ + private async generateGenesisNetworkJson( + namespace: string, + nodeAliases: NodeAliases, + keysDir: string, + stagingDir: string, + ) { + const networkNodeServiceMap = await this.accountManager.getNodeServiceMap(namespace); + + const genesisNetworkData = await GenesisNetworkDataConstructor.initialize( + nodeAliases, + this.keyManager, + keysDir, + networkNodeServiceMap, + ); + + const genesisNetworkJson = path.join(stagingDir, 'genesis-network.json'); + fs.writeFileSync(genesisNetworkJson, genesisNetworkData.toJSON()); + } + prepareStagingDirectory(nodeAliasesProperty: any) { return new Task('Prepare staging directory', (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config; @@ -1215,7 +1244,7 @@ export class NodeCommandTasks { const config = ctx.config; const signingCertFile = Templates.renderGossipPemPublicKeyFile(config.nodeAlias); const signingCertFullPath = path.join(config.keysDir, signingCertFile); - ctx.signingCertDer = this._loadPermCertificate(signingCertFullPath); + ctx.signingCertDer = this.keyManager.getDerFromPemCertificate(signingCertFullPath); }); } @@ -1224,7 +1253,7 @@ export class NodeCommandTasks { const config = ctx.config; const tlsCertFile = Templates.renderTLSPemPublicKeyFile(config.nodeAlias); const tlsCertFullPath = path.join(config.keysDir, tlsCertFile); - const tlsCertDer = this._loadPermCertificate(tlsCertFullPath); + const tlsCertDer = this.keyManager.getDerFromPemCertificate(tlsCertFullPath); ctx.tlsCertHash = crypto.createHash('sha384').update(tlsCertDer).digest(); }); } @@ -1292,7 +1321,7 @@ export class NodeCommandTasks { return new Task('Send node update transaction', async (ctx: any, task: ListrTaskWrapper) => { const config: NodeUpdateConfigClass = ctx.config; - const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias) - 1; + const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias); self.logger.info(`nodeId: ${nodeId}, config.newAccountNumber: ${config.newAccountNumber}`); if (config.existingNodeAliases.length > 1) { @@ -1305,7 +1334,7 @@ export class NodeCommandTasks { if (config.tlsPublicKey && config.tlsPrivateKey) { self.logger.info(`config.tlsPublicKey: ${config.tlsPublicKey}`); - const tlsCertDer = self._loadPermCertificate(config.tlsPublicKey); + const tlsCertDer = self.keyManager.getDerFromPemCertificate(config.tlsPublicKey); const tlsCertHash = crypto.createHash('sha384').update(tlsCertDer).digest(); nodeUpdateTx = nodeUpdateTx.setCertificateHash(tlsCertHash); @@ -1317,7 +1346,7 @@ export class NodeCommandTasks { if (config.gossipPublicKey && config.gossipPrivateKey) { self.logger.info(`config.gossipPublicKey: ${config.gossipPublicKey}`); - const signingCertDer = self._loadPermCertificate(config.gossipPublicKey); + const signingCertDer = self.keyManager.getDerFromPemCertificate(config.gossipPublicKey); nodeUpdateTx = nodeUpdateTx.setGossipCaCertificate(signingCertDer); const publicKeyFile = Templates.renderGossipPemPublicKeyFile(config.nodeAlias); @@ -1378,14 +1407,19 @@ export class NodeCommandTasks { } const index = config.existingNodeAliases.length; - const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias) - 1; + let maxNodeId = 0; + for (const nodeAlias of config.existingNodeAliases) { + const nodeId = config.serviceMap.get(nodeAlias).nodeId; + maxNodeId = Math.max(nodeId, maxNodeId); + } + const nodeId = maxNodeId + 1; let valuesArg = ''; for (let i = 0; i < index; i++) { if (transactionType === NodeSubcommandType.UPDATE && config.newAccountNumber && i === nodeId) { // for the case of updating node // use new account number for this node id - valuesArg += ` --set "hedera.nodes[${i}].accountId=${config.newAccountNumber}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"`; + valuesArg += ` --set "hedera.nodes[${i}].accountId=${config.newAccountNumber}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"--set "hedera.nodes[${i}].node-id=${nodeId}" `; } else if (transactionType !== NodeSubcommandType.DELETE || i !== nodeId) { // for the case of deleting node valuesArg += ` --set "hedera.nodes[${i}].accountId=${config.serviceMap.get(config.existingNodeAliases[i]).accountId}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"`; @@ -1407,8 +1441,7 @@ export class NodeCommandTasks { } const nodeAlias: NodeAlias = config.nodeAlias; - const nodeId: NodeId = Templates.nodeIdFromNodeAlias(nodeAlias); - const nodeIndexInValues = nodeId - 1; + const nodeIndexInValues = Templates.nodeIdFromNodeAlias(nodeAlias); // Set static IPs for HAProxy if (config.haproxyIpsParsed) { @@ -1569,7 +1602,7 @@ export class NodeCommandTasks { async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config; const newNodeFullyQualifiedPodName = Templates.renderNetworkPodName(config.nodeAlias); - const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias) - 1; + const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias); const savedStateDir = config.lastStateZipPath.match(/\/(\d+)\.zip$/)[1]; const savedStatePath = `${constants.HEDERA_HAPI_PATH}/data/saved/com.hedera.services.ServicesMain/${nodeId}/123/${savedStateDir}`; await this.k8.execContainer(newNodeFullyQualifiedPodName, constants.ROOT_CONTAINER, [ @@ -1601,7 +1634,7 @@ export class NodeCommandTasks { const accountMap = getNodeAccountMap(config.existingNodeAliases); const deleteAccountId = accountMap.get(config.nodeAlias); this.logger.debug(`Deleting node: ${config.nodeAlias} with account: ${deleteAccountId}`); - const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias) - 1; + const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias); const nodeDeleteTx = new NodeDeleteTransaction().setNodeId(nodeId).freezeWith(config.nodeClient); const signedTx = await nodeDeleteTx.sign(config.adminKey); diff --git a/src/core/account_manager.ts b/src/core/account_manager.ts index 2a9af9ec9..1e146c306 100644 --- a/src/core/account_manager.ts +++ b/src/core/account_manager.ts @@ -390,15 +390,20 @@ export class AccountManager { // retrieve the list of services and build custom objects for the attributes we need for (const service of serviceList.body.items) { - const serviceType = service.metadata.labels['solo.hedera.com/type']; let serviceBuilder = new NetworkNodeServicesBuilder( service.metadata.labels['solo.hedera.com/node-name'] as NodeAlias, ); if (serviceBuilderMap.has(serviceBuilder.key())) { serviceBuilder = serviceBuilderMap.get(serviceBuilder.key()) as NetworkNodeServicesBuilder; + } else { + serviceBuilder = new NetworkNodeServicesBuilder( + service.metadata.labels['solo.hedera.com/node-name'] as NodeAlias, + ); + serviceBuilder.withNamespace(namespace); } + const serviceType = service.metadata.labels['solo.hedera.com/type']; switch (serviceType) { // solo.hedera.com/type: envoy-proxy-svc case 'envoy-proxy-svc': @@ -413,7 +418,6 @@ export class AccountManager { // solo.hedera.com/type: haproxy-svc case 'haproxy-svc': serviceBuilder - .withAccountId(service.metadata!.labels!['solo.hedera.com/account-id']) .withHaProxyAppSelector(service.spec!.selector!.app) .withHaProxyName(service.metadata!.name as string) .withHaProxyClusterIp(service.spec!.clusterIP as string) @@ -427,6 +431,8 @@ export class AccountManager { // solo.hedera.com/type: network-node-svc case 'network-node-svc': serviceBuilder + .withNodeId(service.metadata!.labels!['solo.hedera.com/node-id']) + .withAccountId(service.metadata!.labels!['solo.hedera.com/account-id']) .withNodeServiceName(service.metadata!.name as string) .withNodeServiceClusterIp(service.spec!.clusterIP as string) .withNodeServiceLoadBalancerIp( diff --git a/src/core/genesis_network_models/genesis_network_data_constructor.ts b/src/core/genesis_network_models/genesis_network_data_constructor.ts index 4498c6e3d..3e3b4bd6e 100644 --- a/src/core/genesis_network_models/genesis_network_data_constructor.ts +++ b/src/core/genesis_network_models/genesis_network_data_constructor.ts @@ -14,34 +14,58 @@ * limitations under the License. * */ -import crypto from 'node:crypto'; -import {PrivateKey} from '@hashgraph/sdk'; -import {Templates} from '../templates.js'; +import {AccountId, PrivateKey} from '@hashgraph/sdk'; import {GenesisNetworkNodeDataWrapper} from './genesis_network_node_data_wrapper.js'; -import * as x509 from '@peculiar/x509'; import * as constants from '../constants.js'; import type {KeyManager} from '../key_manager.js'; import type {ToJSON} from '../../types/index.js'; import type {JsonString, NodeAlias, NodeAliases} from '../../types/aliases.js'; +import {GenesisNetworkRosterEntryDataWrapper} from './genesis_network_roster_entry_data_wrapper.js'; +import {Templates} from '../templates.js'; +import path from 'path'; +import type {NetworkNodeServices} from '../network_node_services.js'; /** * Used to construct the nodes data and convert them to JSON */ export class GenesisNetworkDataConstructor implements ToJSON { public readonly nodes: Record = {}; + public readonly rosters: Record = {}; private constructor( private readonly nodeAliases: NodeAliases, private readonly keyManager: KeyManager, private readonly keysDir: string, + private readonly networkNodeServiceMap: Map, ) { - nodeAliases.forEach((nodeAlias, nodeId) => { - // TODO: get nodeId from label in pod. + nodeAliases.forEach(nodeAlias => { const adminPrivateKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY); const adminPubKey = adminPrivateKey.publicKey; - this.nodes[nodeAlias] = new GenesisNetworkNodeDataWrapper(nodeId, adminPubKey, nodeAlias); + const nodeDataWrapper = new GenesisNetworkNodeDataWrapper( + +networkNodeServiceMap.get(nodeAlias).nodeId, + adminPubKey, + nodeAlias, + ); + this.nodes[nodeAlias] = nodeDataWrapper; + nodeDataWrapper.accountId = AccountId.fromString(networkNodeServiceMap.get(nodeAlias).accountId); + + const rosterDataWrapper = new GenesisNetworkRosterEntryDataWrapper(+networkNodeServiceMap.get(nodeAlias).nodeId); + this.rosters[nodeAlias] = rosterDataWrapper; + rosterDataWrapper.weight = this.nodes[nodeAlias].weight = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT; + + const externalPort = +constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT; + const namespace = networkNodeServiceMap.get(nodeAlias).namespace; + const externalIP = Templates.renderFullyQualifiedNetworkSvcName(namespace, nodeAlias); + // Add gossip endpoints + nodeDataWrapper.addGossipEndpoint(externalIP, externalPort); + rosterDataWrapper.addGossipEndpoint(externalIP, externalPort); + + const haProxyFqdn = Templates.renderFullyQualifiedHaProxyName(nodeAlias, namespace); + + // Add service endpoints + nodeDataWrapper.addServiceEndpoint(haProxyFqdn, constants.GRPC_PORT); }); } @@ -49,8 +73,9 @@ export class GenesisNetworkDataConstructor implements ToJSON { nodeAliases: NodeAliases, keyManager: KeyManager, keysDir: string, + networkNodeServiceMap: Map, ): Promise { - const instance = new GenesisNetworkDataConstructor(nodeAliases, keyManager, keysDir); + const instance = new GenesisNetworkDataConstructor(nodeAliases, keyManager, keysDir, networkNodeServiceMap); await instance.load(); @@ -63,24 +88,29 @@ export class GenesisNetworkDataConstructor implements ToJSON { private async load() { await Promise.all( this.nodeAliases.map(async nodeAlias => { - const nodeKeys = await this.keyManager.loadSigningKey(nodeAlias, this.keysDir); - - //* Convert the certificate to PEM format - const certPem = nodeKeys.certificate.toString(); - - //* Assign the PEM certificate - this.nodes[nodeAlias].gossipCaCertificate = nodeKeys.certificate.toString('base64'); + const signingCertFile = Templates.renderGossipPemPublicKeyFile(nodeAlias); + const signingCertFullPath = path.join(this.keysDir, signingCertFile); + const derCertificate = this.keyManager.getDerFromPemCertificate(signingCertFullPath); - //* Decode the PEM to DER format - const tlsCertDer = new Uint8Array(x509.PemConverter.decode(certPem)[0]); + //* Assign the DER formatted certificate + this.rosters[nodeAlias].gossipCaCertificate = this.nodes[nodeAlias].gossipCaCertificate = + Buffer.from(derCertificate).toString('base64'); //* Generate the SHA-384 hash - this.nodes[nodeAlias].grpcCertificateHash = crypto.createHash('sha384').update(tlsCertDer).digest('base64'); + this.nodes[nodeAlias].grpcCertificateHash = ''; }), ); } public toJSON(): JsonString { - return JSON.stringify({nodeMetadata: Object.values(this.nodes).map(node => node.toObject())}); + const nodeMetadata = []; + Object.keys(this.nodes).forEach(nodeAlias => { + nodeMetadata.push({ + node: this.nodes[nodeAlias].toObject(), + rosterEntry: this.rosters[nodeAlias].toObject(), + }); + }); + + return JSON.stringify({nodeMetadata: nodeMetadata}); } } diff --git a/src/core/genesis_network_models/genesis_network_data_wrapper.ts b/src/core/genesis_network_models/genesis_network_data_wrapper.ts new file mode 100644 index 000000000..9f6786d77 --- /dev/null +++ b/src/core/genesis_network_models/genesis_network_data_wrapper.ts @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import type {NodeId} from '../../types/aliases.js'; +import type {ServiceEndpoint} from '../../types/index.js'; + +export abstract class GenesisNetworkDataWrapper { + public gossipEndpoint: ServiceEndpoint[] = []; + public weight: number; + public gossipCaCertificate: string; + + protected constructor(public readonly nodeId: NodeId) {} + + /** + * @param domainName - a fully qualified domain name + * @param port + */ + public addGossipEndpoint(domainName: string, port: number): void { + this.gossipEndpoint.push({domainName, port, ipAddressV4: ''}); + } +} diff --git a/src/core/genesis_network_models/genesis_network_node_data_wrapper.ts b/src/core/genesis_network_models/genesis_network_node_data_wrapper.ts index 20183c393..04e593225 100644 --- a/src/core/genesis_network_models/genesis_network_node_data_wrapper.ts +++ b/src/core/genesis_network_models/genesis_network_node_data_wrapper.ts @@ -15,24 +15,25 @@ * */ import type {AccountId, PublicKey} from '@hashgraph/sdk'; -import type {GenesisNetworkNodeStructure, ServiceEndpoint, ToObject} from '../../types/index.js'; +import type {GenesisNetworkNodeStructure, NodeAccountId, ServiceEndpoint, ToObject} from '../../types/index.js'; +import {GenesisNetworkDataWrapper} from './genesis_network_data_wrapper.js'; export class GenesisNetworkNodeDataWrapper - implements GenesisNetworkNodeStructure, ToObject<{node: GenesisNetworkNodeStructure}> + extends GenesisNetworkDataWrapper + implements ToObject { public accountId: AccountId; - public gossipEndpoint: ServiceEndpoint[] = []; public serviceEndpoint: ServiceEndpoint[] = []; - public gossipCaCertificate: string; public grpcCertificateHash: string; - public weight: number; public readonly deleted = false; constructor( public readonly nodeId: number, public readonly adminKey: PublicKey, public readonly description: string, - ) {} + ) { + super(nodeId); + } /** * @param domainName - a fully qualified domain name @@ -42,28 +43,18 @@ export class GenesisNetworkNodeDataWrapper this.serviceEndpoint.push({domainName, port, ipAddressV4: ''}); } - /** - * @param domainName - a fully qualified domain name - * @param port - */ - public addGossipEndpoint(domainName: string, port: number): void { - this.gossipEndpoint.push({domainName, port, ipAddressV4: ''}); - } - public toObject() { return { - node: { - nodeId: this.nodeId, - accountId: this.accountId, - description: this.description, - gossipEndpoint: this.gossipEndpoint, - serviceEndpoint: this.serviceEndpoint, - gossipCaCertificate: this.gossipCaCertificate, - grpcCertificateHash: this.grpcCertificateHash, - weight: this.weight, - deleted: this.deleted, - adminKey: this.adminKey, - }, + nodeId: this.nodeId, + accountId: {accountNum: `${this.accountId.num}`} as unknown as NodeAccountId, + description: this.description, + gossipEndpoint: this.gossipEndpoint, + serviceEndpoint: this.serviceEndpoint, + gossipCaCertificate: this.gossipCaCertificate, + grpcCertificateHash: this.grpcCertificateHash, + weight: this.weight, + deleted: this.deleted, + adminKey: this.adminKey, }; } } diff --git a/src/core/genesis_network_models/genesis_network_roster_entry_data_wrapper.ts b/src/core/genesis_network_models/genesis_network_roster_entry_data_wrapper.ts new file mode 100644 index 000000000..9f5a4ee73 --- /dev/null +++ b/src/core/genesis_network_models/genesis_network_roster_entry_data_wrapper.ts @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import {GenesisNetworkDataWrapper} from './genesis_network_data_wrapper.js'; +import type {NodeId} from '../../types/aliases.js'; +import type {GenesisNetworkRosterStructure, ToObject} from '../../types/index.js'; + +export class GenesisNetworkRosterEntryDataWrapper + extends GenesisNetworkDataWrapper + implements GenesisNetworkRosterStructure, ToObject +{ + constructor(public readonly nodeId: NodeId) { + super(nodeId); + } + + public toObject() { + return { + nodeId: this.nodeId, + gossipEndpoint: this.gossipEndpoint, + gossipCaCertificate: this.gossipCaCertificate, + weight: this.weight, + }; + } +} diff --git a/src/core/helpers.ts b/src/core/helpers.ts index c850a4a7e..719b31921 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -226,7 +226,7 @@ export function renameAndCopyFile(srcFilePath: string, expectedBaseName: string, */ export function addDebugOptions(valuesArg: string, debugNodeAlias: NodeAlias, index = 0) { if (debugNodeAlias) { - const nodeId = Templates.nodeIdFromNodeAlias(debugNodeAlias) - 1; + const nodeId = Templates.nodeIdFromNodeAlias(debugNodeAlias); valuesArg += ` --set "hedera.nodes[${nodeId}].root.extraEnv[${index}].name=JAVA_OPTS"`; valuesArg += ` --set "hedera.nodes[${nodeId}].root.extraEnv[${index}].value=-agentlib:jdwp=transport=dt_socket\\,server=y\\,suspend=y\\,address=*:${constants.JVM_DEBUG_PORT}"`; } diff --git a/src/core/key_manager.ts b/src/core/key_manager.ts index 98ac6759f..e7f6b216a 100644 --- a/src/core/key_manager.ts +++ b/src/core/key_manager.ts @@ -509,4 +509,17 @@ export class KeyManager { return subTasks; } + + /** + * Given the path to the PEM certificate (Base64 ASCII), will return the DER (raw binary) bytes + * @param pemCertFullPath + */ + getDerFromPemCertificate(pemCertFullPath: string) { + const certPem = fs.readFileSync(pemCertFullPath).toString(); + const decodedDers = x509.PemConverter.decode(certPem); + if (!decodedDers || decodedDers.length === 0) { + throw new SoloError('unable to load perm key: ' + pemCertFullPath); + } + return new Uint8Array(decodedDers[0]); + } } diff --git a/src/core/network_node_services.ts b/src/core/network_node_services.ts index fccdf8349..722c4c482 100644 --- a/src/core/network_node_services.ts +++ b/src/core/network_node_services.ts @@ -19,6 +19,8 @@ import type {NodeAlias, PodName} from '../types/aliases.js'; export class NetworkNodeServices { public readonly nodeAlias: NodeAlias; + public readonly namespace: string; + public readonly nodeId: string | number; public readonly nodePodName?: PodName; public readonly haProxyName?: string; public readonly haProxyLoadBalancerIp?: string; @@ -41,6 +43,8 @@ export class NetworkNodeServices { constructor(builder: NetworkNodeServicesBuilder) { this.nodeAlias = builder.nodeAlias; + this.namespace = builder.namespace; + this.nodeId = builder.nodeId; this.nodePodName = builder.nodePodName; this.haProxyName = builder.haProxyName; this.haProxyLoadBalancerIp = builder.haProxyLoadBalancerIp; @@ -68,6 +72,8 @@ export class NetworkNodeServices { } export class NetworkNodeServicesBuilder { + public namespace?: string; + public nodeId?: string | number; public haProxyName?: string; public accountId?: string; public haProxyClusterIp!: string; @@ -91,6 +97,16 @@ export class NetworkNodeServicesBuilder { constructor(public readonly nodeAlias: NodeAlias) {} + withNamespace(namespace: string) { + this.namespace = namespace; + return this; + } + + withNodeId(nodeId: string | number) { + this.nodeId = nodeId; + return this; + } + withAccountId(accountId: string) { this.accountId = accountId; return this; diff --git a/src/core/platform_installer.ts b/src/core/platform_installer.ts index b916c118e..c629d949c 100644 --- a/src/core/platform_installer.ts +++ b/src/core/platform_installer.ts @@ -278,10 +278,16 @@ export class PlatformInstaller { } /** Return a list of task to perform node directory setup */ - taskSetup(podName: PodName) { + taskSetup(podName: PodName, stagingDir: string) { const self = this; return new Listr( [ + { + title: 'Copy configuration files', + task: async () => { + await this.copyConfigurationFiles(stagingDir, podName); + }, + }, { title: 'Set file permissions', task: async () => await self.setPlatformDirPermissions(podName), @@ -296,6 +302,17 @@ export class PlatformInstaller { ); } + /** + * Copy configuration files to the network consensus node pod + * @param stagingDir - staging directory path + * @param podName - network consensus node pod name + * @private + */ + private async copyConfigurationFiles(stagingDir: string, podName: `network-node${number}-0`) { + const genesisNetworkJson = [path.join(stagingDir, 'genesis-network.json')]; + await this.copyFiles(podName, genesisNetworkJson, `${constants.HEDERA_HAPI_PATH}/data/config`); + } + /** * Return a list of task to copy the node keys to the staging directory * diff --git a/src/core/profile_manager.ts b/src/core/profile_manager.ts index eaccf31b5..289461ee6 100644 --- a/src/core/profile_manager.ts +++ b/src/core/profile_manager.ts @@ -28,11 +28,9 @@ import * as constants from './constants.js'; import {ConfigManager} from './config_manager.js'; import * as helpers from './helpers.js'; import {getNodeAccountMap} from './helpers.js'; -import {AccountId} from '@hashgraph/sdk'; import type {SemVer} from 'semver'; import {SoloLogger} from './logging.js'; import type {AnyObject, DirPath, NodeAlias, NodeAliases, Path} from '../types/aliases.js'; -import type {GenesisNetworkDataConstructor} from './genesis_network_models/genesis_network_data_constructor.js'; import type {Optional} from '../types/index.js'; import {inject, injectable} from 'tsyringe-neo'; import {patchInject} from './container_helper.js'; @@ -194,12 +192,7 @@ export class ProfileManager { } } - resourcesForConsensusPod( - profile: AnyObject, - nodeAliases: NodeAliases, - yamlRoot: AnyObject, - genesisNetworkData?: GenesisNetworkDataConstructor, - ): AnyObject { + resourcesForConsensusPod(profile: AnyObject, nodeAliases: NodeAliases, yamlRoot: AnyObject): AnyObject { if (!profile) throw new MissingArgumentError('profile is required'); const accountMap = getNodeAccountMap(nodeAliases); @@ -207,6 +200,7 @@ export class ProfileManager { // set consensus pod level resources for (let nodeIndex = 0; nodeIndex < nodeAliases.length; nodeIndex++) { this._setValue(`hedera.nodes.${nodeIndex}.name`, nodeAliases[nodeIndex], yamlRoot); + this._setValue(`hedera.nodes.${nodeIndex}.nodeId`, nodeIndex, yamlRoot); this._setValue(`hedera.nodes.${nodeIndex}.accountId`, accountMap.get(nodeAliases[nodeIndex]), yamlRoot); } @@ -226,7 +220,6 @@ export class ProfileManager { this.configManager.getFlag(flags.releaseTag), this.configManager.getFlag(flags.app), this.configManager.getFlag(flags.chainId), - genesisNetworkData, ); for (const flag of flags.nodeConfigFileFlags.values()) { @@ -269,14 +262,6 @@ export class ProfileManager { yamlRoot, ); - if (genesisNetworkData) { - const genesisNetworkJson = path.join(stagingDir, 'genesis-network.json'); - - fs.writeFileSync(genesisNetworkJson, genesisNetworkData.toJSON()); - - this._setFileContentsAsValue('hedera.configMaps.genesisNetworkJson', genesisNetworkJson, yamlRoot); - } - if (this.configManager.getFlag(flags.applicationEnv)) { this._setFileContentsAsValue( 'hedera.configMaps.applicationEnv', @@ -342,7 +327,7 @@ export class ProfileManager { * @param genesisNetworkData - reference to the constructor * @returns return the full path to the values file */ - public async prepareValuesForSoloChart(profileName: string, genesisNetworkData?: GenesisNetworkDataConstructor) { + public async prepareValuesForSoloChart(profileName: string) { if (!profileName) throw new MissingArgumentError('profileName is required'); const profile = this.getProfile(profileName); @@ -351,7 +336,7 @@ export class ProfileManager { // generate the YAML const yamlRoot = {}; - this.resourcesForConsensusPod(profile, nodeAliases, yamlRoot, genesisNetworkData); + this.resourcesForConsensusPod(profile, nodeAliases, yamlRoot); this.resourcesForHaProxyPod(profile, yamlRoot); this.resourcesForEnvoyProxyPod(profile, yamlRoot); this.resourcesForMinioTenantPod(profile, yamlRoot); @@ -477,7 +462,7 @@ export class ProfileManager { * @param namespace - namespace where the network is deployed * @param nodeAccountMap - the map of node aliases to account IDs * @param destPath - path to the destination directory to write the config.txt file - * @param releaseTag - release tag e.g. v0.42.0 + * @param releaseTagOverride - release tag override * @param [appName] - the app name (default: HederaNode.jar) * @param [chainId] - chain ID (298 for local network) * @param genesisNetworkData @@ -490,7 +475,6 @@ export class ProfileManager { releaseTagOverride: string, appName = constants.HEDERA_APP_NAME, chainId = constants.HEDERA_CHAIN_ID, - genesisNetworkData?: GenesisNetworkDataConstructor, ) { let releaseTag = releaseTagOverride; if (!nodeAccountMap || nodeAccountMap.size === 0) { @@ -508,7 +492,7 @@ export class ProfileManager { const externalPort = +constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT; const nodeStakeAmount = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT; - // @ts-ignore + // @ts-expect-error - TS2353: Object literal may only specify known properties, and includePrerelease does not exist in type Options const releaseVersion = semver.parse(releaseTag, {includePrerelease: true}) as SemVer; try { @@ -522,23 +506,6 @@ export class ProfileManager { const externalIP = Templates.renderFullyQualifiedNetworkSvcName(namespace, nodeAlias); const account = nodeAccountMap.get(nodeAlias); - if (genesisNetworkData) { - // TODO: Use the "nodeSeq" - - const nodeDataWrapper = genesisNetworkData.nodes[nodeAlias]; - - nodeDataWrapper.weight = nodeStakeAmount; - nodeDataWrapper.accountId = AccountId.fromString(account); - - //? Add gossip endpoints - nodeDataWrapper.addGossipEndpoint(externalIP, externalPort); - - const haProxyFqdn = Templates.renderFullyQualifiedHaProxyName(nodeAlias, namespace); - - //? Add service endpoints - nodeDataWrapper.addServiceEndpoint(haProxyFqdn, constants.GRPC_PORT); - } - configLines.push( `address, ${nodeSeq}, ${nodeSeq}, ${nodeAlias}, ${nodeStakeAmount}, ${internalIP}, ${internalPort}, ${externalIP}, ${externalPort}, ${account}`, ); diff --git a/src/core/templates.ts b/src/core/templates.ts index 06f91be0b..a8bdf4a35 100644 --- a/src/core/templates.ts +++ b/src/core/templates.ts @@ -173,7 +173,7 @@ export class Templates { for (let i = nodeAlias.length - 1; i > 0; i--) { // @ts-ignore if (isNaN(nodeAlias[i])) { - return parseInt(nodeAlias.substring(i + 1, nodeAlias.length)); + return parseInt(nodeAlias.substring(i + 1, nodeAlias.length)) - 1; } } diff --git a/src/types/index.ts b/src/types/index.ts index be22a2a13..140aff925 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -103,9 +103,15 @@ export interface ServiceEndpoint { domainName: string; } +export interface NodeAccountId { + accountId: { + accountNum: string; + }; +} + export interface GenesisNetworkNodeStructure { nodeId: number; - accountId: AccountId; + accountId: NodeAccountId; description: string; gossipEndpoint: ServiceEndpoint[]; serviceEndpoint: ServiceEndpoint[]; @@ -115,3 +121,10 @@ export interface GenesisNetworkNodeStructure { deleted: boolean; adminKey: PublicKey; } + +export interface GenesisNetworkRosterStructure { + nodeId: number; + weight: number; + gossipEndpoint: ServiceEndpoint[]; + gossipCaCertificate: string; +}