@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
407 lines • 23.6 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import { after, before, describe, it } from 'mocha';
import { expect } from 'chai';
import { AccountCreateTransaction, AccountId, Client, Hbar, HbarUnit, Logger, LogLevel, PrivateKey, Status, TopicCreateTransaction, TopicMessageSubmitTransaction, } from '@hiero-ledger/sdk';
import * as constants from '../../../src/core/constants.js';
import * as version from '../../../version.js';
import { endToEndTestSuite, getTestCluster, getTestLogger, HEDERA_PLATFORM_VERSION_TAG } from '../../test-utility.js';
import { AccountCommand } from '../../../src/commands/account.js';
import { Flags as flags } from '../../../src/commands/flags.js';
import { Duration } from '../../../src/core/time/duration.js';
import { NamespaceName } from '../../../src/types/namespace/namespace-name.js';
import { container } from 'tsyringe-neo';
import { InjectTokens } from '../../../src/core/dependency-injection/inject-tokens.js';
import * as helpers from '../../../src/core/helpers.js';
import { entityId } from '../../../src/core/helpers.js';
import { Templates } from '../../../src/core/templates.js';
import * as Base64 from 'js-base64';
import { Argv } from '../../helpers/argv-wrapper.js';
import { ValueContainer } from '../../../src/core/dependency-injection/value-container.js';
import { LedgerCommandDefinition } from '../../../src/commands/command-definitions/ledger-command-definition.js';
import { ConsensusCommandDefinition } from '../../../src/commands/command-definitions/consensus-command-definition.js';
const defaultTimeout = Duration.ofSeconds(20).toMillis();
const testName = 'account-cmd-e2e';
const namespace = NamespaceName.of(testName);
const testSystemAccounts = [[3, 5]];
const argv = Argv.getDefaultArgv(namespace);
argv.setArg(flags.forcePortForward, true);
argv.setArg(flags.namespace, namespace.name);
argv.setArg(flags.releaseTag, HEDERA_PLATFORM_VERSION_TAG);
argv.setArg(flags.nodeAliasesUnparsed, 'node1,node2');
argv.setArg(flags.generateGossipKeys, true);
argv.setArg(flags.generateTlsKeys, true);
argv.setArg(flags.clusterRef, getTestCluster());
argv.setArg(flags.soloChartVersion, version.SOLO_CHART_VERSION);
argv.setArg(flags.realm, 0);
argv.setArg(flags.shard, 0);
// enable load balancer for e2e tests
// argv.setArg(flags.loadBalancerEnabled, true);
const overrides = new Map([
[InjectTokens.SystemAccounts, new ValueContainer(InjectTokens.SystemAccounts, testSystemAccounts)],
]);
endToEndTestSuite(testName, argv, { containerOverrides: overrides }, bootstrapResp => {
describe('AccountCommand', () => {
let accountCmd;
let testLogger;
const { opts: { k8Factory, accountManager, configManager, commandInvoker, remoteConfig }, cmd: { nodeCmd }, } = bootstrapResp;
before(async () => {
accountCmd = container.resolve(AccountCommand);
bootstrapResp.cmd.accountCmd = accountCmd;
const localConfig = container.resolve(InjectTokens.LocalConfigRuntimeState);
await localConfig.load();
testLogger = getTestLogger();
});
after(async function () {
this.timeout(Duration.ofMinutes(3).toMillis());
await k8Factory.default().namespaces().delete(namespace);
await accountManager.close();
await nodeCmd.close();
});
describe('node proxies should be UP', () => {
for (const nodeAlias of argv.getArg(flags.nodeAliasesUnparsed).split(',')) {
it(`proxy should be UP: ${nodeAlias} `, async () => {
await k8Factory
.default()
.pods()
.waitForReadyStatus(namespace, [`app=haproxy-${nodeAlias}`, 'solo.hedera.com/type=haproxy'], 300, Duration.ofSeconds(2).toMillis());
}).timeout(Duration.ofSeconds(30).toMillis());
}
});
describe('ledger system init command', () => {
it('should succeed with init command', async () => {
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.SYSTEM_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.SYSTEM_INIT,
callback: async (argv) => accountCmd.init(argv),
});
}).timeout(Duration.ofMinutes(8).toMillis());
describe('special accounts should have new keys', () => {
const genesisKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY);
const realm = argv.getArg(flags.realm);
const shard = argv.getArg(flags.shard);
before(async function () {
this.timeout(Duration.ofSeconds(20).toMillis());
await accountManager.loadNodeClient(namespace, remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward));
});
after(async function () {
this.timeout(Duration.ofSeconds(20).toMillis());
await accountManager.close();
});
it('Node admin key should have been updated, not equal to genesis key', async () => {
const nodeAliases = helpers.parseNodeAliases(argv.getArg(flags.nodeAliasesUnparsed), bootstrapResp.opts.remoteConfig.getConsensusNodes(), bootstrapResp.opts.configManager);
for (const nodeAlias of nodeAliases) {
const keyFromK8 = await k8Factory
.default()
.secrets()
.read(namespace, Templates.renderNodeAdminKeyName(nodeAlias));
const privateKey = Base64.decode(keyFromK8.data.privateKey);
expect(privateKey.toString()).not.to.equal(genesisKey.toString());
}
});
for (const [start, end] of testSystemAccounts) {
for (let index = start; index <= end; index++) {
it(`account ${index} should not have genesis key`, async () => {
expect(accountManager._nodeClient).not.to.be.null;
const accountId = entityId(shard, realm, index);
testLogger.info(`Fetching account keys: accountId ${accountId}`);
const keys = await accountManager.getAccountKeys(accountId);
testLogger.info(`Fetched account keys: accountId ${accountId}`);
expect(keys.length).not.to.equal(0);
expect(keys[0].toString()).not.to.equal(genesisKey.toString());
}).timeout(Duration.ofSeconds(20).toMillis());
}
}
});
});
describe('ledger account create/update command', () => {
let accountId1, accountId2;
it('should create account with no options', async () => {
try {
argv.setArg(flags.amount, 200);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_CREATE,
callback: async (argv) => accountCmd.create(argv),
});
// @ts-expect-error - TS2341: to access private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).not.to.be.null;
accountId1 = accountInfo.accountId;
expect(accountInfo.privateKey).not.to.be.null;
expect(accountInfo.publicKey).not.to.be.null;
expect(accountInfo.balance).to.equal(configManager.getFlag(flags.amount));
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(Duration.ofSeconds(40).toMillis());
it('should create account with private key and hbar amount options', async () => {
try {
argv.setArg(flags.ed25519PrivateKey, constants.GENESIS_KEY);
argv.setArg(flags.amount, 777);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_CREATE,
callback: async (argv) => accountCmd.create(argv),
});
// @ts-expect-error - TS2341: to access private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).not.to.be.null;
accountId2 = accountInfo.accountId;
expect(accountInfo.privateKey.toString()).to.equal(constants.GENESIS_KEY);
expect(accountInfo.publicKey).not.to.be.null;
expect(accountInfo.balance).to.equal(configManager.getFlag(flags.amount));
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(defaultTimeout);
it('should update account-1', async () => {
try {
argv.setArg(flags.amount, 0);
argv.setArg(flags.accountId, accountId1);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_UPDATE,
callback: async (argv) => accountCmd.update(argv),
});
// @ts-expect-error - TS2341: to access private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv.getArg(flags.accountId));
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).not.to.be.null;
expect(accountInfo.balance).to.equal(200);
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(defaultTimeout);
it('should update account-2 with accountId, amount, new private key, and standard out options', async () => {
try {
argv.setArg(flags.accountId, accountId2);
argv.setArg(flags.ed25519PrivateKey, constants.GENESIS_KEY);
argv.setArg(flags.amount, 333);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_UPDATE,
callback: async (argv) => accountCmd.update(argv),
});
// @ts-expect-error - TS2341: to access private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv.getArg(flags.accountId));
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).not.to.be.null;
expect(accountInfo.balance).to.equal(1110);
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(defaultTimeout);
it('should be able to get account-1', async () => {
try {
argv.setArg(flags.accountId, accountId1);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_INFO,
callback: async (argv) => accountCmd.get(argv),
});
// @ts-expect-error - TS2341: to access private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv.getArg(flags.accountId));
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).to.be.ok;
expect(accountInfo.balance).to.equal(200);
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(defaultTimeout);
it('should be able to get account-2', async () => {
try {
argv.setArg(flags.accountId, accountId2);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_INFO,
callback: async (argv) => accountCmd.get(argv),
});
// @ts-expect-error - TS2341: to access private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv.getArg(flags.accountId));
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).to.be.ok;
expect(accountInfo.balance).to.equal(1110);
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(defaultTimeout);
it('should create account with ecdsa private key and set alias', async () => {
const ecdsaPrivateKey = PrivateKey.generateECDSA();
try {
argv.setArg(flags.ecdsaPrivateKey, ecdsaPrivateKey.toString());
argv.setArg(flags.setAlias, true);
await commandInvoker.invoke({
argv: argv,
command: LedgerCommandDefinition.COMMAND_NAME,
subcommand: LedgerCommandDefinition.ACCOUNT_SUBCOMMAND_NAME,
action: LedgerCommandDefinition.ACCOUNT_CREATE,
callback: async (argv) => accountCmd.create(argv),
});
// @ts-expect-error - TS2341: to access private property
const newAccountInfo = accountCmd.accountInfo;
expect(newAccountInfo).not.to.be.null;
expect(newAccountInfo.accountId).not.to.be.null;
expect(newAccountInfo.privateKey.toString()).to.equal(ecdsaPrivateKey.toString());
expect(newAccountInfo.publicKey.toString()).to.equal(ecdsaPrivateKey.publicKey.toString());
expect(newAccountInfo.balance).to.be.greaterThan(0);
const accountId = AccountId.fromString(newAccountInfo.accountId);
expect(newAccountInfo.accountAlias).to.equal(`${accountId.realm}.${accountId.shard}.${ecdsaPrivateKey.publicKey.toEvmAddress()}`);
await accountManager.loadNodeClient(namespace, remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward));
const accountAliasInfo = await accountManager.accountInfoQuery(newAccountInfo.accountAlias);
expect(accountAliasInfo).not.to.be.null;
}
catch (error) {
testLogger.showUserError(error);
expect.fail();
}
}).timeout(defaultTimeout);
});
describe('Test SDK create account and submit transaction', () => {
let accountInfo;
let MY_ACCOUNT_ID;
let MY_PRIVATE_KEY;
it('Create new account', async () => {
try {
await accountManager.loadNodeClient(namespace, remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward));
const privateKey = PrivateKey.generate();
const amount = 100;
const newAccount = await new AccountCreateTransaction()
.setKey(privateKey)
.setInitialBalance(Hbar.from(amount, HbarUnit.Hbar))
.execute(accountManager._nodeClient);
// Get the new account ID
const getReceipt = await newAccount.getReceipt(accountManager._nodeClient);
accountInfo = {
accountId: getReceipt.accountId.toString(),
privateKey: privateKey.toString(),
publicKey: privateKey.publicKey.toString(),
balance: amount,
};
MY_ACCOUNT_ID = accountInfo.accountId;
MY_PRIVATE_KEY = accountInfo.privateKey;
testLogger.info(`Account created: ${JSON.stringify(accountInfo)}`);
expect(accountInfo.accountId).not.to.be.null;
expect(accountInfo.balance).to.equal(amount);
}
catch (error) {
testLogger.showUserError(error);
}
}).timeout(Duration.ofMinutes(2).toMillis());
it('Create client from network config and submit topic/message should succeed', async () => {
try {
// Setup network configuration
const networkConfig = {
['127.0.0.1:30212']: AccountId.fromString('0.0.3'),
['127.0.0.1:30213']: AccountId.fromString('0.0.4'),
};
// Instantiate SDK client
const sdkClient = Client.fromConfig({ network: networkConfig, scheduleNetworkUpdate: false });
sdkClient.setOperator(MY_ACCOUNT_ID, MY_PRIVATE_KEY);
sdkClient.setLogger(new Logger(LogLevel.Trace, 'hashgraph-sdk.log'));
// Create a new public topic and submit a message
const txResponse = await new TopicCreateTransaction().execute(sdkClient);
const receipt = await txResponse.getReceipt(sdkClient);
const submitResponse = await new TopicMessageSubmitTransaction({
topicId: receipt.topicId,
message: 'Hello, Hedera!',
}).execute(sdkClient);
const submitReceipt = await submitResponse.getReceipt(sdkClient);
expect(submitReceipt.status).to.deep.equal(Status.Success);
}
catch (error) {
testLogger.showUserError(error);
}
}).timeout(Duration.ofMinutes(2).toMillis());
it('Enable Envoy proxy port forwarding and create client from network config should succeed', async () => {
try {
// using label `app=envoy-proxy-node1` to find Envoy proxy pod
const envoyProxyPod = await k8Factory.default().pods().list(namespace, ['app=envoy-proxy-node1']);
// enable portfroward of Envoy proxy pod
const portNumber = await k8Factory
.default()
.pods()
.readByReference(envoyProxyPod[0].podReference)
.portForward(10_500, 8080);
// Setup network configuration
const networkConfig = { ['127.0.0.1:10500']: AccountId.fromString('0.0.3') };
// Instantiate SDK client
const sdkClient = Client.fromConfig({ network: networkConfig, scheduleNetworkUpdate: false });
sdkClient.setOperator(MY_ACCOUNT_ID, MY_PRIVATE_KEY);
// Create a new public topic and submit a message
const txResponse = await new TopicCreateTransaction().execute(sdkClient);
const receipt = await txResponse.getReceipt(sdkClient);
const submitResponse = await new TopicMessageSubmitTransaction({
topicId: receipt.topicId,
message: 'Hello, Hedera!',
}).execute(sdkClient);
const submitReceipt = await submitResponse.getReceipt(sdkClient);
expect(submitReceipt.status).to.deep.equal(Status.Success);
// stop port forwarding
await k8Factory.default().pods().readByReference(envoyProxyPod[0].podReference).stopPortForward(portNumber);
}
catch (error) {
testLogger.showUserError(error);
}
}).timeout(Duration.ofMinutes(2).toMillis());
// hitchhiker account test to test node freeze and restart
it('Freeze and restart all nodes should succeed', async () => {
try {
await commandInvoker.invoke({
argv: argv,
command: ConsensusCommandDefinition.COMMAND_NAME,
subcommand: ConsensusCommandDefinition.NETWORK_SUBCOMMAND_NAME,
action: ConsensusCommandDefinition.NETWORK_FREEZE,
callback: async (argv) => nodeCmd.handlers.freeze(argv),
});
await commandInvoker.invoke({
argv: argv,
command: ConsensusCommandDefinition.COMMAND_NAME,
subcommand: ConsensusCommandDefinition.NODE_SUBCOMMAND_NAME,
action: ConsensusCommandDefinition.NODE_RESTART,
callback: async (argv) => nodeCmd.handlers.restart(argv),
});
}
catch (error) {
testLogger.showUserError(error);
}
}).timeout(Duration.ofMinutes(10).toMillis());
});
});
});
//# sourceMappingURL=account.test.js.map