UNPKG

@hashgraph/solo

Version:

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

407 lines 23.6 kB
// 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