UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

176 lines (152 loc) 7.23 kB
// SPDX-License-Identifier: Apache-2.0 import {AccountId, PrivateKey, PublicKey} from '@hiero-ledger/sdk'; import {GenesisNetworkNodeDataWrapper} from './genesis-network-node-data-wrapper.js'; import * as constants from '../constants.js'; import {type KeyManager} from '../key-manager.js'; import {type ToJSON} from '../../types/index.js'; import {type JsonString, type NodeAlias} from '../../types/aliases.js'; import {GenesisNetworkRosterEntryDataWrapper} from './genesis-network-roster-entry-data-wrapper.js'; import {Templates} from '../templates.js'; import {SoloError} from '../errors/solo-error.js'; import {Flags as flags} from '../../commands/flags.js'; import {type AccountManager} from '../account-manager.js'; import {type ConsensusNode} from '../model/consensus-node.js'; import {PathEx} from '../../business/utils/path-ex.js'; import {type NodeServiceMapping} from '../../types/mappings/node-service-mapping.js'; import {type NetworkNodeServices} from '../network-node-services.js'; import {type NamespaceName} from '../../types/namespace/namespace-name.js'; /** * Used to construct the nodes data and convert them to JSON */ export class GenesisNetworkDataConstructor implements ToJSON { public readonly nodes: Record<NodeAlias, GenesisNetworkNodeDataWrapper> = {}; public readonly rosters: Record<NodeAlias, GenesisNetworkRosterEntryDataWrapper> = {}; private readonly initializationPromise: Promise<void>; private constructor( private readonly consensusNodes: ConsensusNode[], private readonly keyManager: KeyManager, private readonly accountManager: AccountManager, private readonly keysDirectory: string, public networkNodeServiceMap: NodeServiceMapping, public adminPublicKeyMap: Map<NodeAlias, string>, public domainNamesMapping?: Record<NodeAlias, string>, ) { this.initializationPromise = (async (): Promise<void> => { for (const consensusNode of consensusNodes) { let adminPublicKey: PublicKey; const networkNodeService: NetworkNodeServices = this.networkNodeServiceMap.get(consensusNode.name); const accountId: AccountId = AccountId.fromString(networkNodeService.accountId); const namespace: NamespaceName = networkNodeService.namespace; if (adminPublicKeyMap.has(consensusNode.name as NodeAlias)) { try { if (PublicKey.fromStringED25519(adminPublicKeyMap.get(consensusNode.name))) { adminPublicKey = PublicKey.fromStringED25519(adminPublicKeyMap.get(consensusNode.name)); } } catch { // Ignore error } } try { // not found existing one, generate a new key, and save to k8s secret if (!adminPublicKey) { const newKey: PrivateKey = PrivateKey.generateED25519(); adminPublicKey = newKey.publicKey; try { await this.accountManager.createOrReplaceAccountKeySecret(newKey, accountId, false, namespace); } catch { throw new SoloError(`failed to create secret for admin key of: ${accountId.toString()}`); } } const nodeDataWrapper: GenesisNetworkNodeDataWrapper = new GenesisNetworkNodeDataWrapper( +networkNodeService.nodeId, adminPublicKey, consensusNode.name, ); this.nodes[consensusNode.name] = nodeDataWrapper; nodeDataWrapper.accountId = accountId; const rosterDataWrapper: GenesisNetworkRosterEntryDataWrapper = new GenesisNetworkRosterEntryDataWrapper( +networkNodeService.nodeId, ); this.rosters[consensusNode.name] = rosterDataWrapper; rosterDataWrapper.weight = this.nodes[consensusNode.name].weight = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT; const externalPort: number = +constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT; // Add gossip endpoints nodeDataWrapper.addGossipEndpoint(networkNodeService.externalAddress, externalPort); rosterDataWrapper.addGossipEndpoint(networkNodeService.externalAddress, externalPort); const domainName: string = domainNamesMapping?.[consensusNode.name]; // Add service endpoints nodeDataWrapper.addServiceEndpoint(domainName ?? networkNodeService.externalAddress, constants.GRPC_PORT); } catch (error) { throw new SoloError(error.message, error); } } })(); } public static async initialize( consensusNodes: ConsensusNode[], keyManager: KeyManager, accountManager: AccountManager, keysDirectory: string, networkNodeServiceMap: NodeServiceMapping, adminPublicKeys: string[], domainNamesMapping?: Record<NodeAlias, string>, ): Promise<GenesisNetworkDataConstructor> { const adminPublicKeyMap: Map<NodeAlias, string> = new Map(); let adminPublicKeyIsDefaultValue: boolean = true; for (const publicKey of adminPublicKeys) { if (publicKey !== flags.adminPublicKeys.definition.defaultValue) { adminPublicKeyIsDefaultValue = false; } } // If admin keys are passed and if it is not the default value from flags then validate and build the adminPublicKeyMap if (adminPublicKeys.length > 0 && !adminPublicKeyIsDefaultValue) { if (adminPublicKeys.length !== consensusNodes.length) { throw new SoloError( `Provide a comma separated list of DER encoded ED25519 public keys for each node, adminPublicKeys.length=${adminPublicKeys.length} does not match consensusNodes.length=${consensusNodes.length}`, ); } for (const [index, key] of adminPublicKeys.entries()) { adminPublicKeyMap.set(consensusNodes[index].name, key); } } const instance = new GenesisNetworkDataConstructor( consensusNodes, keyManager, accountManager, keysDirectory, networkNodeServiceMap, adminPublicKeyMap, domainNamesMapping, ); await instance.load(); return instance; } /** * Loads the gossipCaCertificate and grpcCertificateHash */ private async load() { await this.initializationPromise; await Promise.all( this.consensusNodes.map(async consensusNode => { const signingCertFile = Templates.renderGossipPemPublicKeyFile(consensusNode.name as NodeAlias); const signingCertFullPath = PathEx.joinWithRealPath(this.keysDirectory, signingCertFile); const derCertificate = this.keyManager.getDerFromPemCertificate(signingCertFullPath); //* Assign the DER formatted certificate this.rosters[consensusNode.name].gossipCaCertificate = this.nodes[consensusNode.name].gossipCaCertificate = Buffer.from(derCertificate).toString('base64'); //* Generate the SHA-384 hash this.nodes[consensusNode.name].grpcCertificateHash = ''; }), ); } public toJSON(): JsonString { const nodeMetadata = []; for (const nodeAlias of Object.keys(this.nodes)) { nodeMetadata.push({ node: this.nodes[nodeAlias].toObject(), rosterEntry: this.rosters[nodeAlias].toObject(), }); } return JSON.stringify({nodeMetadata: nodeMetadata}); } }