diff --git a/.github/workflows/zxc-e2e-test.yaml b/.github/workflows/zxc-e2e-test.yaml index 3bf1b57f6..eb064a635 100644 --- a/.github/workflows/zxc-e2e-test.yaml +++ b/.github/workflows/zxc-e2e-test.yaml @@ -167,7 +167,7 @@ jobs: if: ${{ runner.os == 'linux' && (inputs.npm-test-script == 'test-e2e-node-local-hedera' || inputs.npm-test-script == 'test-e2e-node-local-ptt' || inputs.npm-test-script == 'test-e2e-node-add-local') && !cancelled() && !failure() }} run: | cd .. - git clone https://github.com/hashgraph/hedera-services.git --depth 1 --branch v0.57.3 + git clone https://github.com/hashgraph/hedera-services.git --depth 1 --branch v0.58.0 cd hedera-services ls -ltr ${{ env.CG_EXEC }} ./gradlew assemble --stacktrace --info diff --git a/Taskfile.yml b/Taskfile.yml index 37a255a69..342ebaca4 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -7,7 +7,7 @@ env: SOLO_NETWORK_SIZE: 2 SOLO_NAMESPACE: solo-e2e SOLO_CHART_VERSION: 0.36.3 - CONSENSUS_NODE_VERSION: v0.57.3 + CONSENSUS_NODE_VERSION: v0.58.0 vars: use_port_forwards: "true" diff --git a/examples/custom-network-config/Taskfile.yml b/examples/custom-network-config/Taskfile.yml index bfe97d02e..51aa756f1 100644 --- a/examples/custom-network-config/Taskfile.yml +++ b/examples/custom-network-config/Taskfile.yml @@ -7,7 +7,7 @@ env: SOLO_NETWORK_SIZE: 10 SOLO_NAMESPACE: solo-alex-kuzmin-n4 SOLO_CHART_VERSION: 0.36.3 - CONSENSUS_NODE_VERSION: v0.57.3 + CONSENSUS_NODE_VERSION: v0.58.0 VALUES_FLAG: "--values-file {{.USER_WORKING_DIR}}/init-containers-values.yaml" SETTINGS_FLAG: "--settings-txt {{.USER_WORKING_DIR}}/settings.txt" SOLO_HOME: "/Users/user/.solo-alex-kuzmin-n4" diff --git a/examples/performance-tuning/Latitude/Taskfile.yml b/examples/performance-tuning/Latitude/Taskfile.yml index a82e80d68..f22c7fca7 100644 --- a/examples/performance-tuning/Latitude/Taskfile.yml +++ b/examples/performance-tuning/Latitude/Taskfile.yml @@ -7,7 +7,7 @@ env: SOLO_NETWORK_SIZE: 10 SOLO_NAMESPACE: solo-perf-lat SOLO_CHART_VERSION: 0.36.3 - CONSENSUS_NODE_VERSION: v0.57.3 + CONSENSUS_NODE_VERSION: v0.58.0 VALUES_FLAG: "--values-file {{.USER_WORKING_DIR}}/init-containers-values.yaml" SETTINGS_FLAG: "--settings-txt {{.USER_WORKING_DIR}}/settings.txt" SOLO_HOME: "/Users/user/.solo-perf-lat" diff --git a/examples/performance-tuning/solo-perf-test/Taskfile.yml b/examples/performance-tuning/solo-perf-test/Taskfile.yml index ff2901e3c..3961c47ff 100644 --- a/examples/performance-tuning/solo-perf-test/Taskfile.yml +++ b/examples/performance-tuning/solo-perf-test/Taskfile.yml @@ -7,7 +7,7 @@ env: SOLO_NETWORK_SIZE: 7 SOLO_NAMESPACE: solo-perf-test SOLO_CHART_VERSION: 0.36.3 - CONSENSUS_NODE_VERSION: v0.57.3 + CONSENSUS_NODE_VERSION: v0.58.0 VALUES_FLAG: "--values-file {{.USER_WORKING_DIR}}/init-containers-values.yaml" SETTINGS_FLAG: "--settings-txt {{.USER_WORKING_DIR}}/settings.txt" SOLO_HOME: "/Users/user/.solo-perf-test" diff --git a/examples/solo-gke-test/Taskfile.yml b/examples/solo-gke-test/Taskfile.yml index be3496025..6be48f5c1 100644 --- a/examples/solo-gke-test/Taskfile.yml +++ b/examples/solo-gke-test/Taskfile.yml @@ -9,7 +9,7 @@ env: SOLO_NETWORK_SIZE: 5 SOLO_NAMESPACE: solo-gke-test SOLO_CHART_VERSION: 0.36.3 - CONSENSUS_NODE_VERSION: v0.57.3 + CONSENSUS_NODE_VERSION: v0.58.0 VALUES_FLAG: "--values-file {{.USER_WORKING_DIR}}/init-containers-values.yaml" SETTINGS_FLAG: "--settings-txt {{.USER_WORKING_DIR}}/settings.txt" SOLO_HOME: "{{.solo_home_override_dir}}" diff --git a/resources/templates/application.properties b/resources/templates/application.properties index ae8714f90..aa70f28fb 100644 --- a/resources/templates/application.properties +++ b/resources/templates/application.properties @@ -14,4 +14,5 @@ hedera.profiles.active=TEST # TODO: this is a workaround until prepareUpgrade freeze will recalculate the weight prior to writing the config.txt staking.periodMins=1 nodes.updateAccountIdAllowed=true - +# TODO: remove once we have the uploader enabled, required for current p0 deadline +blockStream.streamMode=RECORDS diff --git a/src/commands/network.ts b/src/commands/network.ts index a85009b12..ea6db9bdc 100644 --- a/src/commands/network.ts +++ b/src/commands/network.ts @@ -38,6 +38,7 @@ 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'; export interface NetworkDeployConfigClass { applicationEnv: string; @@ -61,6 +62,7 @@ export interface NetworkDeployConfigClass { grpcWebTlsCertificatePath: string; grpcTlsKeyPath: string; grpcWebTlsKeyPath: string; + genesisNetworkData: GenesisNetworkDataConstructor; getUnusedConfigs: () => string[]; haproxyIps: string; envoyIps: string; @@ -130,20 +132,19 @@ export class NetworkCommand extends BaseCommand { ]; } - async prepareValuesArg( - config: { - chartDirectory?: string; - app?: string; - nodeAliases?: string[]; - debugNodeAlias?: NodeAlias; - enablePrometheusSvcMonitor?: boolean; - releaseTag?: string; - persistentVolumeClaims?: string; - valuesFile?: string; - haproxyIpsParsed?: Record; - envoyIpsParsed?: Record; - } = {}, - ) { + async prepareValuesArg(config: { + chartDirectory?: string; + app?: string; + nodeAliases: string[]; + debugNodeAlias?: NodeAlias; + enablePrometheusSvcMonitor?: boolean; + releaseTag?: string; + persistentVolumeClaims?: string; + valuesFile?: string; + haproxyIpsParsed?: Record; + envoyIpsParsed?: Record; + genesisNetworkData: GenesisNetworkDataConstructor; + }) { let valuesArg = config.chartDirectory ? `-f ${path.join(config.chartDirectory, 'solo-deployment', 'values.yaml')}` : ''; @@ -160,7 +161,10 @@ export class NetworkCommand extends BaseCommand { } const profileName = this.configManager.getFlag(flags.profileName) as string; - this.profileValuesFile = await this.profileManager.prepareValuesForSoloChart(profileName); + this.profileValuesFile = await this.profileManager.prepareValuesForSoloChart( + profileName, + config.genesisNetworkData, + ); if (this.profileValuesFile) { valuesArg += this.prepareValuesFiles(this.profileValuesFile); } @@ -172,7 +176,7 @@ export class NetworkCommand extends BaseCommand { // Iterate over each node and set static IPs for HAProxy if (config.haproxyIpsParsed) { - config.nodeAliases?.forEach((nodeAlias, index) => { + config.nodeAliases.forEach((nodeAlias, index) => { const ip = config.haproxyIpsParsed?.[nodeAlias]; if (ip) valuesArg += ` --set "hedera.nodes[${index}].haproxyStaticIP=${ip}"`; @@ -181,7 +185,7 @@ export class NetworkCommand extends BaseCommand { // Iterate over each node and set static IPs for Envoy Proxy if (config.envoyIpsParsed) { - config.nodeAliases?.forEach((nodeAlias, index) => { + config.nodeAliases.forEach((nodeAlias, index) => { const ip = config.envoyIpsParsed?.[nodeAlias]; if (ip) valuesArg += ` --set "hedera.nodes[${index}].envoyProxyStaticIP=${ip}"`; @@ -253,13 +257,19 @@ export class NetworkCommand extends BaseCommand { constants.SOLO_DEPLOYMENT_CHART, ); - config.valuesArg = await this.prepareValuesArg(config); - // compute other config parameters config.keysDir = path.join(validatePath(config.cacheDir), 'keys'); 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.valuesArg = await this.prepareValuesArg(config); + if (!(await this.k8.hasNamespace(config.namespace))) { await this.k8.createNamespace(config.namespace); } @@ -341,7 +351,7 @@ export class NetworkCommand extends BaseCommand { }, { title: 'Check if cluster setup chart is installed', - task: async (ctx, task) => { + task: async () => { const isChartInstalled = await this.chartManager.isChartInstalled('', constants.SOLO_CLUSTER_SETUP_CHART); if (!isChartInstalled) { throw new SoloError( @@ -386,7 +396,7 @@ export class NetworkCommand extends BaseCommand { task: (ctx, parentTask) => { const config = ctx.config; - // set up the sub-tasks + // set up the subtasks return parentTask.newListr(self.platformInstaller.copyNodeKeys(config.stagingDir, config.nodeAliases), { concurrent: true, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, @@ -502,7 +512,7 @@ export class NetworkCommand extends BaseCommand { ), }); - // set up the sub-tasks + // set up the subtasks return task.newListr(subTasks, { concurrent: false, // no need to run concurrently since if one node is up, the rest should be up by then rendererOptions: { @@ -754,6 +764,7 @@ export class NetworkCommand extends BaseCommand { }, }; } + /** Adds the consensus node, envoy and haproxy components to remote config. */ public addNodesAndProxies(): ListrTask { return { diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index d3c7b50e3..105dacd5d 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -24,8 +24,9 @@ import path from 'path'; import fs from 'fs'; import {validatePath} from '../../core/helpers.js'; import {Flags as flags} from '../flags.js'; -import {type NodeAlias, type NodeAliases, type PodName} from '../../types/aliases.js'; -import {type NetworkNodeServices} from '../../core/network_node_services.js'; +import type {NodeAlias, NodeAliases, PodName} from '../../types/aliases.js'; +import type {NetworkNodeServices} from '../../core/network_node_services.js'; +import {type NodeAddConfigClass} from './node_add_config.js'; export const PREPARE_UPGRADE_CONFIGS_NAME = 'prepareUpgradeConfig'; export const DOWNLOAD_GENERATED_FILES_CONFIGS_NAME = 'downloadGeneratedFilesConfig'; @@ -357,46 +358,6 @@ export interface NodeStartConfigClass { nodeAliasesUnparsed: string; } -export interface NodeAddConfigClass { - app: string; - cacheDir: string; - chainId: string; - chartDirectory: string; - devMode: boolean; - debugNodeAlias: NodeAlias; - endpointType: string; - soloChartVersion: string; - generateGossipKeys: boolean; - generateTlsKeys: boolean; - gossipEndpoints: string; - grpcEndpoints: string; - localBuildPath: string; - namespace: string; - nodeAlias: NodeAlias; - releaseTag: string; - adminKey: PrivateKey; - allNodeAliases: NodeAliases; - chartPath: string; - curDate: Date; - existingNodeAliases: NodeAliases; - freezeAdminPrivateKey: string; - keysDir: string; - lastStateZipPath: string; - nodeClient: any; - podNames: Record; - serviceMap: Map; - treasuryKey: PrivateKey; - stagingDir: string; - stagingKeysDir: string; - grpcTlsCertificatePath: string; - grpcWebTlsCertificatePath: string; - grpcTlsKeyPath: string; - grpcWebTlsKeyPath: string; - haproxyIps: string; - envoyIps: string; - getUnusedConfigs: () => string[]; -} - export interface NodeDeleteConfigClass { app: string; cacheDir: string; diff --git a/src/commands/node/node_add_config.ts b/src/commands/node/node_add_config.ts new file mode 100644 index 000000000..f5129c909 --- /dev/null +++ b/src/commands/node/node_add_config.ts @@ -0,0 +1,59 @@ +/** + * 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 {NodeAlias, NodeAliases, PodName} from '../../types/aliases.js'; +import type {NetworkNodeServices} from '../../core/network_node_services.js'; +import {type PrivateKey} from '@hashgraph/sdk'; + +export interface NodeAddConfigClass { + app: string; + cacheDir: string; + chainId: string; + chartDirectory: string; + devMode: boolean; + debugNodeAlias: NodeAlias; + endpointType: string; + soloChartVersion: string; + generateGossipKeys: boolean; + generateTlsKeys: boolean; + gossipEndpoints: string; + grpcEndpoints: string; + localBuildPath: string; + namespace: string; + nodeAlias: NodeAlias; + releaseTag: string; + adminKey: PrivateKey; + allNodeAliases: NodeAliases; + chartPath: string; + curDate: Date; + existingNodeAliases: NodeAliases; + freezeAdminPrivateKey: string; + keysDir: string; + lastStateZipPath: string; + nodeClient: any; + podNames: Record; + serviceMap: Map; + treasuryKey: PrivateKey; + stagingDir: string; + stagingKeysDir: string; + grpcTlsCertificatePath: string; + grpcWebTlsCertificatePath: string; + grpcTlsKeyPath: string; + grpcWebTlsKeyPath: string; + haproxyIps: string; + envoyIps: string; + getUnusedConfigs: () => string[]; +} diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index 3e143bcc8..482dbca28 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -31,7 +31,6 @@ import { FREEZE_ADMIN_ACCOUNT, HEDERA_NODE_DEFAULT_STAKE_AMOUNT, IGNORED_NODE_ACCOUNT_ID, - LOCAL_HOST, TREASURY_ACCOUNT_ID, } from '../../core/constants.js'; import { @@ -75,16 +74,12 @@ import { } from '../../types/aliases.js'; import {NodeStatusCodes, NodeStatusEnums, NodeSubcommandType} from '../../core/enumerations.js'; import * as x509 from '@peculiar/x509'; -import type { - NodeAddConfigClass, - NodeDeleteConfigClass, - NodeRefreshConfigClass, - NodeUpdateConfigClass, -} from './configs.js'; +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'; export class NodeCommandTasks { private readonly accountManager: AccountManager; @@ -455,6 +450,7 @@ export class NodeCommandTasks { */ _generateGossipKeys(generateMultiple: boolean) { const self = this; + return new Task( 'Generate gossip keys', (ctx: any, task: ListrTaskWrapper) => { @@ -701,7 +697,7 @@ export class NodeCommandTasks { config.stagingDir, ); - // if directory data/upgrade/current/data/keys does not exist then use data/upgrade/current + // if directory data/upgrade/current/data/keys does not exist, then use data/upgrade/current let keyDir = `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/data/keys`; if (!(await self.k8.hasDir(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, keyDir))) { keyDir = `${constants.HEDERA_HAPI_PATH}/data/upgrade/current`; diff --git a/src/core/account_manager.ts b/src/core/account_manager.ts index d760471cc..4af1b9987 100644 --- a/src/core/account_manager.ts +++ b/src/core/account_manager.ts @@ -294,7 +294,6 @@ export class AccountManager { const host = networkNodeService.haProxyLoadBalancerIp as string; const targetPort = port; try { - await this.k8.testSocketConnection(host, targetPort); obj[`${host}:${targetPort}`] = accountId; await this.pingNetworkNode(obj, accountId); this.logger.debug(`using load balancer IP: ${host}:${targetPort}`); @@ -312,8 +311,6 @@ export class AccountManager { this._portForwards.push(await this.k8.portForward(networkNodeService.haProxyPodName, localPort, port)); } - await this.k8.testSocketConnection(host, targetPort); - this.logger.debug(`using local host port forward: ${host}:${targetPort}`); obj[`${host}:${targetPort}`] = accountId; diff --git a/src/core/constants.ts b/src/core/constants.ts index cfc767c1e..abe827b35 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -196,6 +196,7 @@ export const RELAY_PODS_RUNNING_MAX_ATTEMPTS = +process.env.RELAY_PODS_RUNNING_M export const RELAY_PODS_RUNNING_DELAY = +process.env.RELAY_PODS_RUNNING_DELAY || 1_000; export const RELAY_PODS_READY_MAX_ATTEMPTS = +process.env.RELAY_PODS_READY_MAX_ATTEMPTS || 100; export const RELAY_PODS_READY_DELAY = +process.env.RELAY_PODS_READY_DELAY || 1_000; +export const GRPC_PORT = +process.env.GRPC_PORT || 50_211; export const NETWORK_DESTROY_WAIT_TIMEOUT = +process.env.NETWORK_DESTROY_WAIT_TIMEOUT || 120; diff --git a/src/core/genesis_network_models/genesis_network_data_constructor.ts b/src/core/genesis_network_models/genesis_network_data_constructor.ts new file mode 100644 index 000000000..4498c6e3d --- /dev/null +++ b/src/core/genesis_network_models/genesis_network_data_constructor.ts @@ -0,0 +1,86 @@ +/** + * 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 crypto from 'node:crypto'; +import {PrivateKey} from '@hashgraph/sdk'; +import {Templates} from '../templates.js'; +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'; + +/** + * Used to construct the nodes data and convert them to JSON + */ +export class GenesisNetworkDataConstructor implements ToJSON { + public readonly nodes: Record = {}; + + private constructor( + private readonly nodeAliases: NodeAliases, + private readonly keyManager: KeyManager, + private readonly keysDir: string, + ) { + nodeAliases.forEach((nodeAlias, nodeId) => { + // TODO: get nodeId from label in pod. + const adminPrivateKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY); + const adminPubKey = adminPrivateKey.publicKey; + + this.nodes[nodeAlias] = new GenesisNetworkNodeDataWrapper(nodeId, adminPubKey, nodeAlias); + }); + } + + public static async initialize( + nodeAliases: NodeAliases, + keyManager: KeyManager, + keysDir: string, + ): Promise { + const instance = new GenesisNetworkDataConstructor(nodeAliases, keyManager, keysDir); + + await instance.load(); + + return instance; + } + + /** + * Loads the gossipCaCertificate and grpcCertificateHash + */ + 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'); + + //* Decode the PEM to DER format + const tlsCertDer = new Uint8Array(x509.PemConverter.decode(certPem)[0]); + + //* Generate the SHA-384 hash + this.nodes[nodeAlias].grpcCertificateHash = crypto.createHash('sha384').update(tlsCertDer).digest('base64'); + }), + ); + } + + public toJSON(): JsonString { + return JSON.stringify({nodeMetadata: Object.values(this.nodes).map(node => node.toObject())}); + } +} 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 new file mode 100644 index 000000000..20183c393 --- /dev/null +++ b/src/core/genesis_network_models/genesis_network_node_data_wrapper.ts @@ -0,0 +1,69 @@ +/** + * 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 {AccountId, PublicKey} from '@hashgraph/sdk'; +import type {GenesisNetworkNodeStructure, ServiceEndpoint, ToObject} from '../../types/index.js'; + +export class GenesisNetworkNodeDataWrapper + implements GenesisNetworkNodeStructure, ToObject<{node: GenesisNetworkNodeStructure}> +{ + 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, + ) {} + + /** + * @param domainName - a fully qualified domain name + * @param port + */ + public addServiceEndpoint(domainName: string, port: number): void { + 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, + }, + }; + } +} diff --git a/src/core/helpers.ts b/src/core/helpers.ts index 4d83c2c8a..ca9329195 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -27,6 +27,7 @@ import {type NodeAlias, type NodeAliases} from '../types/aliases.js'; import {type CommandFlag} from '../types/flag_types.js'; import {type SoloLogger} from './logging.js'; import {type Duration} from './time/duration.js'; +import {type NodeAddConfigClass} from '../commands/node/node_add_config.js'; export function sleep(duration: Duration) { return new Promise(resolve => { @@ -241,13 +242,14 @@ export function addDebugOptions(valuesArg: string, debugNodeAlias: NodeAlias, in export function addSaveContextParser(ctx: any) { const exportedCtx = {} as Record; - const config = /** @type {NodeAddConfigClass} **/ ctx.config; + const config = ctx.config as NodeAddConfigClass; const exportedFields = ['tlsCertHash', 'upgradeZipHash', 'newNode']; exportedCtx.signingCertDer = ctx.signingCertDer.toString(); exportedCtx.gossipEndpoints = ctx.gossipEndpoints.map((ep: any) => `${ep.getDomainName}:${ep.getPort}`); exportedCtx.grpcServiceEndpoints = ctx.grpcServiceEndpoints.map((ep: any) => `${ep.getDomainName}:${ep.getPort}`); exportedCtx.adminKey = ctx.adminKey.toString(); + // @ts-ignore exportedCtx.existingNodeAliases = config.existingNodeAliases; for (const prop of exportedFields) { @@ -308,16 +310,14 @@ export function prepareEndpoints(endpointType: string, endpoints: string[], defa if (endpointType.toUpperCase() === constants.ENDPOINT_TYPE_IP) { ret.push( new ServiceEndpoint({ - // @ts-ignore - port, + port: +port, ipAddressV4: parseIpAddressToUint8Array(url), }), ); } else { ret.push( new ServiceEndpoint({ - // @ts-ignore - port, + port: +port, domainName: url, }), ); diff --git a/src/core/profile_manager.ts b/src/core/profile_manager.ts index cf0d89923..61a07d790 100644 --- a/src/core/profile_manager.ts +++ b/src/core/profile_manager.ts @@ -20,7 +20,6 @@ import {SoloError, IllegalArgumentError, MissingArgumentError} from './errors.js import * as yaml from 'yaml'; import dot from 'dot-object'; import * as semver from 'semver'; -import type {SemVer} from 'semver'; import {readFile, writeFile} from 'fs/promises'; import {Flags as flags} from '../commands/flags.js'; @@ -29,8 +28,12 @@ import * as constants from './constants.js'; import {type 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 type {SoloLogger} from './logging.js'; -import type {NodeAlias, NodeAliases} from '../types/aliases.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'; const consensusSidecars = [ 'recordStreamUploader', @@ -43,12 +46,12 @@ const consensusSidecars = [ export class ProfileManager { private readonly logger: SoloLogger; private readonly configManager: ConfigManager; - private readonly cacheDir: string; + private readonly cacheDir: DirPath; - private profiles: Map; - private profileFile: string | undefined; + private profiles: Map; + private profileFile: Optional; - constructor(logger: SoloLogger, configManager: ConfigManager, cacheDir: string = constants.SOLO_VALUES_DIR) { + constructor(logger: SoloLogger, configManager: ConfigManager, cacheDir: DirPath = constants.SOLO_VALUES_DIR) { if (!logger) throw new MissingArgumentError('An instance of core/SoloLogger is required'); if (!configManager) throw new MissingArgumentError('An instance of core/ConfigManager is required'); @@ -61,7 +64,15 @@ export class ProfileManager { this.cacheDir = cacheDir; } - loadProfiles(forceReload = false): Map { + /** + * Load profiles from a profile file and populate the profiles map. + * + * @param [forceReload = false] - forces the profiles map to override even if it exists. + * @returns reference to the populated profiles map. + * + * @throws {IllegalArgumentError} if the profile file is not found. + */ + loadProfiles(forceReload = false): Map { const profileFile = this.configManager.getFlag(flags.profileFile); if (!profileFile) throw new MissingArgumentError('profileFile is required'); @@ -75,7 +86,7 @@ export class ProfileManager { // load profile file this.profiles = new Map(); const yamlData = fs.readFileSync(profileFile, 'utf8'); - const profileItems = yaml.parse(yamlData) as Record; + const profileItems = yaml.parse(yamlData) as Record; // add profiles for (const key in profileItems) { @@ -88,26 +99,36 @@ export class ProfileManager { return this.profiles; } - getProfile(profileName: string): object { + /** + * Get profile from the profiles map, loads them on demand if they are not loaded already. + * + * @param profileName - profile name (key in the map). + * @returns the profile. + * + * @throws {IllegalArgumentError} if profiles can't be loaded or the profile name is not found in the map. + */ + getProfile(profileName: string): AnyObject { if (!profileName) throw new MissingArgumentError('profileName is required'); if (!this.profiles || this.profiles.size <= 0) { this.loadProfiles(); } - if (!this.profiles || !this.profiles.has(profileName)) + if (!this.profiles || !this.profiles.has(profileName)) { throw new IllegalArgumentError(`Profile does not exists with name: ${profileName}`); - return this.profiles.get(profileName) as object; + } + + return this.profiles.get(profileName) as AnyObject; } /** - * Set value in the yaml object + * Set value in the YAML object * @param itemPath - item path in the yaml * @param value - value to be set - * @param yamlRoot - root of the yaml object + * @param yamlRoot - root of the YAML object * @returns */ - _setValue(itemPath: string, value: any, yamlRoot: object): object { - // find the location where to set the value in the yaml + _setValue(itemPath: string, value: any, yamlRoot: AnyObject): AnyObject { + // find the location where to set the value in the YAML const itemPathParts: string[] = itemPath.split('.'); let parent = yamlRoot; let current = parent; @@ -115,7 +136,7 @@ export class ProfileManager { for (let itemPathPart of itemPathParts) { if (helpers.isNumeric(itemPathPart)) { // @ts-ignore - itemPathPart = Number.parseInt(itemPathPart); // numeric path part can only be array index i.e. an integer + itemPathPart = Number.parseInt(itemPathPart); // numeric path part can only be array index i.e., an integer if (!Array.isArray(parent[prevItemPath])) { parent[prevItemPath] = []; } @@ -144,12 +165,12 @@ export class ProfileManager { /** * Set items for the chart - * @param itemPath - item path in the yaml, if empty then root of the yaml object will be used + * @param itemPath - item path in the YAML, if empty then root of the YAML object will be used * @param items - the element object - * @param yamlRoot - root of the yaml object to update + * @param yamlRoot - root of the YAML object to update * @private */ - _setChartItems(itemPath: string, items: any, yamlRoot: object) { + _setChartItems(itemPath: string, items: any, yamlRoot: AnyObject) { if (!items) return; const dotItems = dot.dot(items); @@ -157,7 +178,7 @@ export class ProfileManager { for (const key in dotItems) { let itemKey = key; - // if it is an array key like extraEnv[0].JAVA_OPTS, convert it into dot separated key as extraEnv.0.JAVA_OPTS + // if it is an array key like extraEnv[0].JAVA_OPTS, convert it into a dot separated key as extraEnv.0.JAVA_OPTS if (key.indexOf('[') !== -1) { itemKey = key.replace('[', '.').replace(']', ''); } @@ -170,7 +191,12 @@ export class ProfileManager { } } - resourcesForConsensusPod(profile: any, nodeAliases: NodeAliases, yamlRoot: object): object { + resourcesForConsensusPod( + profile: AnyObject, + nodeAliases: NodeAliases, + yamlRoot: AnyObject, + genesisNetworkData?: GenesisNetworkDataConstructor, + ): AnyObject { if (!profile) throw new MissingArgumentError('profile is required'); const accountMap = getNodeAccountMap(nodeAliases); @@ -197,6 +223,7 @@ 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()) { @@ -238,6 +265,15 @@ export class ProfileManager { path.join(stagingDir, 'templates', 'bootstrap.properties'), 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', @@ -259,26 +295,26 @@ export class ProfileManager { return yamlRoot; } - resourcesForHaProxyPod(profile: any, yamlRoot: object) { + private resourcesForHaProxyPod(profile: AnyObject, yamlRoot: AnyObject) { if (!profile) throw new MissingArgumentError('profile is required'); if (!profile.haproxy) return; // use chart defaults return this._setChartItems('defaults.haproxy', profile.haproxy, yamlRoot); } - resourcesForEnvoyProxyPod(profile: any, yamlRoot: object) { + private resourcesForEnvoyProxyPod(profile: AnyObject, yamlRoot: AnyObject) { if (!profile) throw new MissingArgumentError('profile is required'); if (!profile.envoyProxy) return; // use chart defaults return this._setChartItems('defaults.envoyProxy', profile.envoyProxy, yamlRoot); } - resourcesForHederaExplorerPod(profile: any, yamlRoot: object) { + private resourcesForHederaExplorerPod(profile: AnyObject, yamlRoot: AnyObject) { if (!profile) throw new MissingArgumentError('profile is required'); if (!profile.explorer) return; return this._setChartItems('', profile.explorer, yamlRoot); } - resourcesForMinioTenantPod(profile: any, yamlRoot: object) { + private resourcesForMinioTenantPod(profile: AnyObject, yamlRoot: AnyObject) { if (!profile) throw new MissingArgumentError('profile is required'); // @ts-ignore if (!profile.minio || !profile.minio.tenant) return; // use chart defaults @@ -299,37 +335,29 @@ export class ProfileManager { /** * Prepare a values file for Solo Helm chart - * @param profileName resource profile name + * @param profileName - resource profile name + * @param genesisNetworkData - reference to the constructor * @returns return the full path to the values file */ - prepareValuesForSoloChart(profileName: string) { + public async prepareValuesForSoloChart(profileName: string, genesisNetworkData?: GenesisNetworkDataConstructor) { if (!profileName) throw new MissingArgumentError('profileName is required'); const profile = this.getProfile(profileName); const nodeAliases = helpers.parseNodeAliases(this.configManager.getFlag(flags.nodeAliasesUnparsed)); if (!nodeAliases) throw new SoloError('Node IDs are not set in the config'); - // generate the yaml + // generate the YAML const yamlRoot = {}; - this.resourcesForConsensusPod(profile, nodeAliases, yamlRoot); + this.resourcesForConsensusPod(profile, nodeAliases, yamlRoot, genesisNetworkData); this.resourcesForHaProxyPod(profile, yamlRoot); this.resourcesForEnvoyProxyPod(profile, yamlRoot); this.resourcesForMinioTenantPod(profile, yamlRoot); - // write the yaml const cachedValuesFile = path.join(this.cacheDir, `solo-${profileName}.yaml`); - return new Promise((resolve, reject) => { - fs.writeFile(cachedValuesFile, yaml.stringify(yamlRoot), err => { - if (err) { - reject(err); - } - - resolve(cachedValuesFile); - }); - }); + return this.writeToYaml(cachedValuesFile, yamlRoot); } - async bumpHederaConfigVersion(applicationPropertiesPath: string) { + private async bumpHederaConfigVersion(applicationPropertiesPath: string) { const lines = (await readFile(applicationPropertiesPath, 'utf-8')).split('\n'); for (const line of lines) { @@ -343,23 +371,14 @@ export class ProfileManager { await writeFile(applicationPropertiesPath, lines.join('\n')); } - async prepareValuesForNodeAdd(configTxtPath: string, applicationPropertiesPath: string) { + public async prepareValuesForNodeAdd(configTxtPath: string, applicationPropertiesPath: string) { const yamlRoot = {}; this._setFileContentsAsValue('hedera.configMaps.configTxt', configTxtPath, yamlRoot); await this.bumpHederaConfigVersion(applicationPropertiesPath); this._setFileContentsAsValue('hedera.configMaps.applicationProperties', applicationPropertiesPath, yamlRoot); - // write the yaml const cachedValuesFile = path.join(this.cacheDir, 'solo-node-add.yaml'); - return new Promise((resolve, reject) => { - fs.writeFile(cachedValuesFile, yaml.stringify(yamlRoot), err => { - if (err) { - reject(err); - } - - resolve(cachedValuesFile); - }); - }); + return this.writeToYaml(cachedValuesFile, yamlRoot); } /** @@ -367,38 +386,38 @@ export class ProfileManager { * @param profileName - resource profile name * @returns return the full path to the values file */ - prepareValuesForRpcRelayChart(profileName: string) { + public async prepareValuesForRpcRelayChart(profileName: string) { if (!profileName) throw new MissingArgumentError('profileName is required'); - const profile = this.getProfile(profileName) as any; + const profile = this.getProfile(profileName) as AnyObject; if (!profile.rpcRelay) return Promise.resolve(); // use chart defaults - // generate the yaml + // generate the YAML const yamlRoot = {}; this._setChartItems('', profile.rpcRelay, yamlRoot); - // write the yaml const cachedValuesFile = path.join(this.cacheDir, `rpcRelay-${profileName}.yaml`); - return new Promise((resolve, reject) => { - fs.writeFile(cachedValuesFile, yaml.stringify(yamlRoot), err => { - if (err) { - reject(err); - } - - resolve(cachedValuesFile); - }); - }); + return this.writeToYaml(cachedValuesFile, yamlRoot); } - prepareValuesHederaExplorerChart(profileName: string) { + public async prepareValuesHederaExplorerChart(profileName: string) { if (!profileName) throw new MissingArgumentError('profileName is required'); - const profile = this.getProfile(profileName) as any; - // generate the yaml + const profile = this.getProfile(profileName) as AnyObject; + // generate the YAML const yamlRoot = {}; this.resourcesForHederaExplorerPod(profile, yamlRoot); - // write the yaml const cachedValuesFile = path.join(this.cacheDir, `explorer-${profileName}.yaml`); - return new Promise((resolve, reject) => { + return this.writeToYaml(cachedValuesFile, yamlRoot); + } + + /** + * Writes the YAML to file. + * + * @param cachedValuesFile - the target file to write the YAML root to. + * @param yamlRoot - object to turn into YAML and write to file. + */ + private async writeToYaml(cachedValuesFile: Path, yamlRoot: AnyObject) { + return await new Promise((resolve, reject) => { fs.writeFile(cachedValuesFile, yaml.stringify(yamlRoot), err => { if (err) { reject(err); @@ -412,14 +431,14 @@ export class ProfileManager { /** * Prepare a values file for mirror-node Helm chart * @param profileName - resource profile name - * @returns return the full path to the values file + * @returns the full path to the values file */ - prepareValuesForMirrorNodeChart(profileName: string) { + public async prepareValuesForMirrorNodeChart(profileName: string) { if (!profileName) throw new MissingArgumentError('profileName is required'); - const profile = this.getProfile(profileName) as any; + const profile = this.getProfile(profileName) as AnyObject; if (!profile.mirror) return Promise.resolve(); // use chart defaults - // generate the yaml + // generate the YAML const yamlRoot = {}; if (profile.mirror.postgresql) { if (profile.mirror.postgresql.persistence) { @@ -435,26 +454,17 @@ export class ProfileManager { this._setChartItems('grpc', profile.mirror.grpc, yamlRoot); this._setChartItems('monitor', profile.mirror.monitor, yamlRoot); - // write the yaml const cachedValuesFile = path.join(this.cacheDir, `mirror-${profileName}.yaml`); - return new Promise((resolve, reject) => { - fs.writeFile(cachedValuesFile, yaml.stringify(yamlRoot), err => { - if (err) { - reject(err); - } - - resolve(cachedValuesFile); - }); - }); + return this.writeToYaml(cachedValuesFile, yamlRoot); } /** - * Writes the contents of a file as a value for the given nested item path in the yaml object - * @param itemPath - nested item path in the yaml object to store the file contents - * @param valueFilePath - path to the file whose contents will be stored in the yaml object - * @param yamlRoot - root of the yaml object + * Writes the contents of a file as a value for the given nested item path in the YAML object + * @param itemPath - nested item path in the YAML object to store the file contents + * @param valueFilePath - path to the file whose contents will be stored in the YAML object + * @param yamlRoot - root of the YAML object */ - private _setFileContentsAsValue(itemPath: string, valueFilePath: string, yamlRoot: object) { + private _setFileContentsAsValue(itemPath: string, valueFilePath: string, yamlRoot: AnyObject) { const fileContents = fs.readFileSync(valueFilePath, 'utf8'); this._setValue(itemPath, fileContents, yamlRoot); } @@ -467,6 +477,7 @@ export class ProfileManager { * @param releaseTag - release tag e.g. v0.42.0 * @param [appName] - the app name (default: HederaNode.jar) * @param [chainId] - chain ID (298 for local network) + * @param genesisNetworkData * @returns the config.txt file path */ prepareConfigTxt( @@ -476,17 +487,21 @@ export class ProfileManager { releaseTag: string, appName = constants.HEDERA_APP_NAME, chainId = constants.HEDERA_CHAIN_ID, + genesisNetworkData?: GenesisNetworkDataConstructor, ) { - if (!nodeAccountMap || nodeAccountMap.size === 0) + if (!nodeAccountMap || nodeAccountMap.size === 0) { throw new MissingArgumentError('nodeAccountMap the map of node IDs to account IDs is required'); + } + if (!releaseTag) throw new MissingArgumentError('release tag is required'); - if (!fs.existsSync(destPath)) + if (!fs.existsSync(destPath)) { throw new IllegalArgumentError(`config destPath does not exist: ${destPath}`, destPath); + } // init variables - const internalPort = constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT; - const externalPort = constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT; + const internalPort = +constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT; + const externalPort = +constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT; const nodeStakeAmount = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT; // @ts-ignore @@ -501,8 +516,25 @@ export class ProfileManager { for (const nodeAlias of nodeAccountMap.keys()) { const internalIP = Templates.renderFullyQualifiedNetworkPodName(namespace, nodeAlias); 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}`, ); @@ -519,7 +551,7 @@ export class ProfileManager { fs.writeFileSync(configFilePath, configLines.join('\n')); return configFilePath; - } catch (e: Error | any) { + } catch (e: Error | unknown) { throw new SoloError('failed to generate config.txt', e); } } diff --git a/src/core/templates.ts b/src/core/templates.ts index 17a65eab0..a4274bd59 100644 --- a/src/core/templates.ts +++ b/src/core/templates.ts @@ -23,7 +23,7 @@ import {type AccountId} from '@hashgraph/sdk'; import type {IP, NodeAlias, NodeId, PodName} from '../types/aliases.js'; import {GrpcProxyTlsEnums} from './enumerations.js'; import {type ContextClusterStructure} from '../types/config_types.js'; -import type {Cluster, Context} from './config/remote/types.js'; +import type {Cluster, Context, Namespace} from './config/remote/types.js'; export class Templates { public static renderNetworkPodName(nodeAlias: NodeAlias): PodName { @@ -263,6 +263,10 @@ export class Templates { return `haproxy-${nodeAlias}`; } + public static renderFullyQualifiedHaProxyName(nodeAlias: NodeAlias, namespace: Namespace): string { + return `${Templates.renderHaProxyName(nodeAlias)}-svc.${namespace}.svc.cluster.local`; + } + public static parseNodeAliasToIpMapping(unparsed: string): Record { const mapping: Record = {}; diff --git a/src/types/aliases.ts b/src/types/aliases.ts index 40c6337f0..a4cfffe7d 100644 --- a/src/types/aliases.ts +++ b/src/types/aliases.ts @@ -39,6 +39,16 @@ export type TaskFunction = ( export type ConfigBuilder = (argv, ctx, task, shouldLoadNodeClient?) => Promise; +export type Nullable = T | null; + export type IP = string; +export type JsonString = string; + +export type Path = string; +export type FilePath = string; +export type DirPath = string; + +export type AnyObject = Record; + export type SdkNetworkEndpoint = `${string}:${number}`; diff --git a/src/types/index.ts b/src/types/index.ts index 655a49334..be22a2a13 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -18,7 +18,9 @@ import type * as x509 from '@peculiar/x509'; import type net from 'net'; import type * as WebSocket from 'ws'; import type crypto from 'crypto'; -import type {ListrTask} from 'listr2'; +import type {ListrTask, ListrTaskWrapper} from 'listr2'; +import type {AccountId, PublicKey} from '@hashgraph/sdk'; +import type {JsonString} from './aliases.js'; // NOTE: DO NOT add any Solo imports in this file to avoid circular dependencies @@ -77,6 +79,39 @@ export interface ToObject { toObject(): T; } +/** + * Interface for converting class to JSON string. + */ +export interface ToJSON { + /** + * Converts the class instance to a plain JSON string. + * + * @returns the plain JSON string of the class. + */ + toJSON(): JsonString; +} + export type SoloListrTask = ListrTask; export type EmptyContextConfig = object; + +export type SoloListrTaskWrapper = ListrTaskWrapper; + +export interface ServiceEndpoint { + ipAddressV4?: string; + port: number; + domainName: string; +} + +export interface GenesisNetworkNodeStructure { + nodeId: number; + accountId: AccountId; + description: string; + gossipEndpoint: ServiceEndpoint[]; + serviceEndpoint: ServiceEndpoint[]; + gossipCaCertificate: string; + grpcCertificateHash: string; + weight: number; + deleted: boolean; + adminKey: PublicKey; +} diff --git a/test/data/local-config.yaml b/test/data/local-config.yaml index fdac33ca3..ad1342c1f 100644 --- a/test/data/local-config.yaml +++ b/test/data/local-config.yaml @@ -6,4 +6,4 @@ deployments: currentDeploymentName: deployment-1 clusterContextMapping: cluster-1: context-1 - cluster-2: context-2 \ No newline at end of file + cluster-2: context-2 diff --git a/test/unit/commands/network.test.ts b/test/unit/commands/network.test.ts index 86fea8b34..57a0988c7 100644 --- a/test/unit/commands/network.test.ts +++ b/test/unit/commands/network.test.ts @@ -40,6 +40,7 @@ import {ProfileManager} from '../../../src/core/profile_manager.js'; import {KeyManager} from '../../../src/core/key_manager.js'; import {ROOT_DIR} from '../../../src/core/constants.js'; import {ListrLease} from '../../../src/core/lease/listr_lease.js'; +import {GenesisNetworkDataConstructor} from '../../../src/core/genesis_network_models/genesis_network_data_constructor.js'; const getBaseCommandOpts = () => ({ logger: sinon.stub(), @@ -112,6 +113,8 @@ describe('NetworkCommand unit tests', () => { opts.configManager = new ConfigManager(opts.logger); opts.leaseManager = new LeaseManager(opts.k8, opts.configManager, opts.logger, new IntervalLeaseRenewalService()); opts.leaseManager.currentNamespace = sinon.stub().returns(testName); + + GenesisNetworkDataConstructor.initialize = sinon.stub().returns(null); }); it('Install function is called with expected parameters', async () => { diff --git a/tsconfig.json b/tsconfig.json index ec1fa8b60..5832eae0a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig-google.json", "compilerOptions": { + "isolatedModules": true, "target": "ES2022", "lib": [ "ES2022", diff --git a/version.ts b/version.ts index 2f408860f..fb867b700 100644 --- a/version.ts +++ b/version.ts @@ -19,7 +19,6 @@ * This file should only contain versions for dependencies */ -export const JAVA_VERSION = '21.0.1+12'; export const HELM_VERSION = 'v3.14.2'; export const SOLO_CHART_VERSION = '0.38.2'; export const HEDERA_PLATFORM_VERSION = 'v0.58.0';