@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
316 lines • 17.8 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 '@hashgraph/sdk';
import * as constants from '../../../src/core/constants.js';
import * as version from '../../../version.js';
import { e2eTestSuite, getDefaultArgv, HEDERA_PLATFORM_VERSION_TAG, TEST_CLUSTER, testLogger } from '../../test_util.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/core/kube/resources/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 { Templates } from '../../../src/core/templates.js';
import * as Base64 from 'js-base64';
const defaultTimeout = Duration.ofSeconds(20).toMillis();
const testName = 'account-cmd-e2e';
const namespace = NamespaceName.of(testName);
const testSystemAccounts = [[3, 5]];
const argv = getDefaultArgv(namespace);
argv[flags.forcePortForward.name] = true;
argv[flags.namespace.name] = namespace.name;
argv[flags.releaseTag.name] = HEDERA_PLATFORM_VERSION_TAG;
argv[flags.nodeAliasesUnparsed.name] = 'node1,node2';
argv[flags.generateGossipKeys.name] = true;
argv[flags.generateTlsKeys.name] = true;
argv[flags.clusterRef.name] = TEST_CLUSTER;
argv[flags.soloChartVersion.name] = version.SOLO_CHART_VERSION;
argv[flags.loadBalancerEnabled.name] = true;
// set the env variable SOLO_CHARTS_DIR if developer wants to use local Solo charts
argv[flags.chartDirectory.name] = process.env.SOLO_CHARTS_DIR ?? undefined;
// enable load balancer for e2e tests
argv[flags.loadBalancerEnabled.name] = true;
e2eTestSuite(testName, argv, undefined, undefined, undefined, undefined, undefined, undefined, true, bootstrapResp => {
describe('AccountCommand', async () => {
let accountCmd;
let k8Factory;
let accountManager;
let configManager;
let nodeCmd;
before(() => {
accountCmd = new AccountCommand(bootstrapResp.opts, testSystemAccounts);
bootstrapResp.cmd.accountCmd = accountCmd;
k8Factory = bootstrapResp.opts.k8Factory;
accountManager = bootstrapResp.opts.accountManager;
configManager = bootstrapResp.opts.configManager;
nodeCmd = bootstrapResp.cmd.nodeCmd;
});
after(async function () {
this.timeout(Duration.ofMinutes(3).toMillis());
await container.resolve(InjectTokens.NetworkNodes).getLogs(namespace);
await k8Factory.default().namespaces().delete(namespace);
await accountManager.close();
await nodeCmd.close();
});
describe('node proxies should be UP', () => {
for (const nodeAlias of argv[flags.nodeAliasesUnparsed.name].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('account init command', () => {
it('should succeed with init command', async () => {
const status = await accountCmd.init(argv);
expect(status).to.be.ok;
}).timeout(Duration.ofMinutes(3).toMillis());
describe('special accounts should have new keys', () => {
const genesisKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY);
const realm = constants.HEDERA_NODE_ACCOUNT_ID_START.realm;
const shard = constants.HEDERA_NODE_ACCOUNT_ID_START.shard;
before(async function () {
this.timeout(Duration.ofSeconds(20).toMillis());
const clusterRefs = accountCmd.getClusterRefs();
await accountManager.loadNodeClient(namespace, clusterRefs, argv[flags.deployment.name], argv[flags.forcePortForward.name]);
});
after(async function () {
this.timeout(Duration.ofSeconds(20).toMillis());
await accountManager.close();
});
it('Node admin key should have been updated, not eqaul to genesis key', async () => {
const nodeAliases = helpers.parseNodeAliases(argv[flags.nodeAliasesUnparsed.name]);
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 i = start; i <= end; i++) {
it(`account ${i} should not have genesis key`, async () => {
expect(accountManager._nodeClient).not.to.be.null;
const accountId = `${realm}.${shard}.${i}`;
nodeCmd.logger.info(`Fetching account keys: accountId ${accountId}`);
const keys = await accountManager.getAccountKeys(accountId);
nodeCmd.logger.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('account create/update command', () => {
let accountId1, accountId2;
it('should create account with no options', async () => {
try {
argv[flags.amount.name] = 200;
expect(await accountCmd.create(argv)).to.be.true;
// @ts-ignore to access the 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 (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(Duration.ofSeconds(40).toMillis());
it('should create account with private key and hbar amount options', async () => {
try {
argv[flags.ed25519PrivateKey.name] = constants.GENESIS_KEY;
argv[flags.amount.name] = 777;
configManager.update(argv);
expect(await accountCmd.create(argv)).to.be.true;
// @ts-ignore to access the 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 (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(defaultTimeout);
it('should update account-1', async () => {
try {
argv[flags.amount.name] = 0;
argv[flags.accountId.name] = accountId1;
configManager.update(argv);
expect(await accountCmd.update(argv)).to.be.true;
// @ts-ignore to access the private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv[flags.accountId.name]);
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).not.to.be.null;
expect(accountInfo.balance).to.equal(200);
}
catch (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(defaultTimeout);
it('should update account-2 with accountId, amount, new private key, and standard out options', async () => {
try {
argv[flags.accountId.name] = accountId2;
argv[flags.ed25519PrivateKey.name] = constants.GENESIS_KEY;
argv[flags.amount.name] = 333;
configManager.update(argv);
expect(await accountCmd.update(argv)).to.be.true;
// @ts-ignore to access the private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv[flags.accountId.name]);
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).not.to.be.null;
expect(accountInfo.balance).to.equal(1_110);
}
catch (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(defaultTimeout);
it('should be able to get account-1', async () => {
try {
argv[flags.accountId.name] = accountId1;
configManager.update(argv);
expect(await accountCmd.get(argv)).to.be.true;
// @ts-ignore to access the private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv[flags.accountId.name]);
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).to.be.ok;
expect(accountInfo.balance).to.equal(200);
}
catch (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(defaultTimeout);
it('should be able to get account-2', async () => {
try {
argv[flags.accountId.name] = accountId2;
configManager.update(argv);
expect(await accountCmd.get(argv)).to.be.true;
// @ts-ignore to access the private property
const accountInfo = accountCmd.accountInfo;
expect(accountInfo).not.to.be.null;
expect(accountInfo.accountId).to.equal(argv[flags.accountId.name]);
expect(accountInfo.privateKey).to.be.undefined;
expect(accountInfo.publicKey).to.be.ok;
expect(accountInfo.balance).to.equal(1_110);
}
catch (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(defaultTimeout);
it('should create account with ecdsa private key and set alias', async () => {
const ecdsaPrivateKey = PrivateKey.generateECDSA();
try {
argv[flags.ecdsaPrivateKey.name] = ecdsaPrivateKey.toString();
argv[flags.setAlias.name] = true;
configManager.update(argv);
expect(await accountCmd.create(argv)).to.be.true;
// @ts-ignore to access the 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()}`);
const clusterRefs = accountCmd.getClusterRefs();
await accountManager.loadNodeClient(namespace, clusterRefs, argv[flags.deployment.name], argv[flags.forcePortForward.name]);
const accountAliasInfo = await accountManager.accountInfoQuery(newAccountInfo.accountAlias);
expect(accountAliasInfo).not.to.be.null;
}
catch (e) {
testLogger.showUserError(e);
expect.fail();
}
}).timeout(defaultTimeout);
});
describe('Test SDK create account and submit transaction', () => {
const accountManager = bootstrapResp.opts.accountManager;
const networkCmd = bootstrapResp.cmd.networkCmd;
let accountInfo;
let MY_ACCOUNT_ID;
let MY_PRIVATE_KEY;
it('Create new account', async () => {
try {
const clusterRefs = accountCmd.getClusterRefs();
await accountManager.loadNodeClient(namespace, clusterRefs, argv[flags.deployment.name], argv[flags.forcePortForward.name]);
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;
networkCmd.logger.info(`Account created: ${JSON.stringify(accountInfo)}`);
expect(accountInfo.accountId).not.to.be.null;
expect(accountInfo.balance).to.equal(amount);
}
catch (e) {
networkCmd.logger.showUserError(e);
}
}).timeout(Duration.ofMinutes(2).toMillis());
it('Create client from network config and submit topic/message should succeed', async () => {
try {
// Setup network configuration
const networkConfig = {};
networkConfig['127.0.0.1:30212'] = AccountId.fromString('0.0.3');
networkConfig['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 (e) {
networkCmd.logger.showUserError(e);
}
}).timeout(Duration.ofMinutes(2).toMillis());
});
});
});
//# sourceMappingURL=account.test.js.map