@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
128 lines • 6.9 kB
JavaScript
// 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 { 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 { PathEx } from '../../business/utils/path-ex.js';
/**
* Used to construct the nodes data and convert them to JSON
*/
export class GenesisNetworkDataConstructor {
consensusNodes;
keyManager;
accountManager;
keysDirectory;
networkNodeServiceMap;
adminPublicKeyMap;
domainNamesMapping;
nodes = {};
rosters = {};
initializationPromise;
constructor(consensusNodes, keyManager, accountManager, keysDirectory, networkNodeServiceMap, adminPublicKeyMap, domainNamesMapping) {
this.consensusNodes = consensusNodes;
this.keyManager = keyManager;
this.accountManager = accountManager;
this.keysDirectory = keysDirectory;
this.networkNodeServiceMap = networkNodeServiceMap;
this.adminPublicKeyMap = adminPublicKeyMap;
this.domainNamesMapping = domainNamesMapping;
this.initializationPromise = (async () => {
for (const consensusNode of consensusNodes) {
let adminPublicKey;
const networkNodeService = this.networkNodeServiceMap.get(consensusNode.name);
const accountId = AccountId.fromString(networkNodeService.accountId);
const namespace = networkNodeService.namespace;
if (adminPublicKeyMap.has(consensusNode.name)) {
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.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 = new GenesisNetworkNodeDataWrapper(+networkNodeService.nodeId, adminPublicKey, consensusNode.name);
this.nodes[consensusNode.name] = nodeDataWrapper;
nodeDataWrapper.accountId = accountId;
const rosterDataWrapper = new GenesisNetworkRosterEntryDataWrapper(+networkNodeService.nodeId);
this.rosters[consensusNode.name] = rosterDataWrapper;
rosterDataWrapper.weight = this.nodes[consensusNode.name].weight = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT;
const externalPort = +constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT;
// Add gossip endpoints
nodeDataWrapper.addGossipEndpoint(networkNodeService.externalAddress, externalPort);
rosterDataWrapper.addGossipEndpoint(networkNodeService.externalAddress, externalPort);
const domainName = domainNamesMapping?.[consensusNode.name];
// Add service endpoints
nodeDataWrapper.addServiceEndpoint(domainName ?? networkNodeService.externalAddress, constants.GRPC_PORT);
}
catch (error) {
throw new SoloError(error.message, error);
}
}
})();
}
static async initialize(consensusNodes, keyManager, accountManager, keysDirectory, networkNodeServiceMap, adminPublicKeys, domainNamesMapping) {
const adminPublicKeyMap = new Map();
let adminPublicKeyIsDefaultValue = 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
*/
async load() {
await this.initializationPromise;
await Promise.all(this.consensusNodes.map(async (consensusNode) => {
const signingCertFile = Templates.renderGossipPemPublicKeyFile(consensusNode.name);
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 = '';
}));
}
toJSON() {
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 });
}
}
//# sourceMappingURL=genesis-network-data-constructor.js.map