Skip to content

Commit

Permalink
feat: add solo smoke test to test flow (#905)
Browse files Browse the repository at this point in the history
Signed-off-by: Jeffrey Tang <[email protected]>
Signed-off-by: Jeromy Cannon <[email protected]>
Co-authored-by: Jeromy Cannon <[email protected]>
  • Loading branch information
JeffreyDallas and jeromy-cannon authored Dec 13, 2024
1 parent b378937 commit 79410f0
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/flow-task-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,6 @@ jobs:
- name: Run Example Task File Test
run: |
task default-with-relay
sleep 10
.github/workflows/script/solo_smoke_test.sh
task clean
135 changes: 135 additions & 0 deletions .github/workflows/script/solo_smoke_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/bin/bash
set -eo pipefail

#
# This script should be called after solo has been deployed with mirror node and relay node deployed,
# and should be called from the root of the solo repository
#
# This uses solo account creation function to repeatedly generate background transactions
# Then run smart contract test, and also javascript sdk sample test to interact with solo network
#

function_name=""

function enable_port_forward ()
{
kubectl port-forward -n solo-e2e svc/haproxy-node1-svc 50211:50211 > /dev/null 2>&1 &
kubectl port-forward -n solo-e2e svc/hedera-explorer 8080:80 > /dev/null 2>&1 &
kubectl port-forward -n solo-e2e svc/relay-node1-hedera-json-rpc-relay 7546:7546 > /dev/null 2>&1 &
kubectl port-forward -n solo-e2e svc/mirror-grpc 5600:5600 > /dev/null 2>&1 &
}

function clone_smart_contract_repo ()
{
echo "Clone hedera-smart-contracts"
if [ -d "hedera-smart-contracts" ]; then
echo "Directory hedera-smart-contracts exists."
else
echo "Directory hedera-smart-contracts does not exist."
git clone https://github.com/hashgraph/hedera-smart-contracts --branch only-erc20-tests
fi
}

function setup_smart_contract_test ()
{
echo "Setup smart contract test"
cd hedera-smart-contracts

echo "Remove previous .env file"
rm -f .env

npm install
npx hardhat compile || return 1:

echo "Build .env file"

echo "PRIVATE_KEYS=\"$CONTRACT_TEST_KEYS\"" > .env
echo "RETRY_DELAY=5000 # ms" >> .env
echo "MAX_RETRY=5" >> .env
cat .env
cd -
}

function start_background_transactions ()
{
echo "Start background transaction"
# generate accounts as background traffic for two minutes
# so record stream files can be kept pushing to mirror node
cd solo
npm run solo-test -- account create -n solo-e2e --create-amount 15 > /dev/null 2>&1 &
cd -
}

function start_contract_test ()
{
cd hedera-smart-contracts
echo "Wait a few seconds for background transactions to start"
sleep 5
echo "Run smart contract test"
npm run hh:test
result=$?

cd -
return $result
}

function create_test_account ()
{
echo "Create test account with solo network"
cd solo

# create new account and extract account id
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
export OPERATOR_ID=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
echo "OPERATOR_ID=${OPERATOR_ID}"
rm test.log

# get private key of the account
npm run solo-test -- account get -n solo-e2e --account-id ${OPERATOR_ID} --private-key > test.log
export OPERATOR_KEY=$(grep "privateKey" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
export CONTRACT_TEST_KEY_ONE=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
echo "CONTRACT_TEST_KEY_ONE=${CONTRACT_TEST_KEY_ONE}"
rm test.log

npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
export SECOND_KEY=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
npm run solo-test -- account get -n solo-e2e --account-id ${SECOND_KEY} --private-key > test.log
export CONTRACT_TEST_KEY_TWO=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
echo "CONTRACT_TEST_KEY_TWO=${CONTRACT_TEST_KEY_TWO}"
rm test.log

export CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEY_ONE},$'\n'${CONTRACT_TEST_KEY_TWO}
export HEDERA_NETWORK="local-node"

echo "OPERATOR_KEY=${OPERATOR_KEY}"
echo "HEDERA_NETWORK=${HEDERA_NETWORK}"
echo "CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEYS}"

cd -
}

function start_sdk_test ()
{
cd solo
node examples/create-topic.js
result=$?

cd -
return $result
}

echo "Restart port-forward"
task helper:clean:port-forward
enable_port_forward


echo "Change to parent directory"
cd ../
create_test_account
clone_smart_contract_repo
setup_smart_contract_test
start_background_transactions
start_contract_test
start_sdk_test
echo "Sleep a while to wait background transactions to finish"
sleep 30
62 changes: 62 additions & 0 deletions examples/create-topic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {Wallet, LocalProvider, TopicCreateTransaction, TopicMessageSubmitTransaction} from '@hashgraph/sdk';

import dotenv from 'dotenv';

dotenv.config();

async function main() {
if (process.env.OPERATOR_ID === null || process.env.OPERATOR_KEY === null || process.env.HEDERA_NETWORK === null) {
throw new Error('Environment variables OPERATOR_ID, HEDERA_NETWORK, and OPERATOR_KEY are required.');
}

console.log(`Hedera network = ${process.env.HEDERA_NETWORK}`);
const provider = new LocalProvider();

const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);

try {
console.log('before create topic');
// create topic
let transaction = await new TopicCreateTransaction().freezeWithSigner(wallet);
transaction = await transaction.signWithSigner(wallet);
console.log('after sign transaction');
const createResponse = await transaction.executeWithSigner(wallet);
const createReceipt = await createResponse.getReceiptWithSigner(wallet);

console.log(`topic id = ${createReceipt.topicId.toString()}`);

// send one message
let topicMessageSubmitTransaction = await new TopicMessageSubmitTransaction({
topicId: createReceipt.topicId,
message: 'Hello World',
}).freezeWithSigner(wallet);
topicMessageSubmitTransaction = await topicMessageSubmitTransaction.signWithSigner(wallet);
const sendResponse = await topicMessageSubmitTransaction.executeWithSigner(wallet);

const sendReceipt = await sendResponse.getReceiptWithSigner(wallet);

console.log(`topic sequence number = ${sendReceipt.topicSequenceNumber.toString()}`);
} catch (error) {
console.error(error);
}

provider.close();
}

void main();
42 changes: 36 additions & 6 deletions src/commands/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {FREEZE_ADMIN_ACCOUNT} from '../core/constants.js';
import {type Opts} from '../types/command_types.js';
import {ListrLease} from '../core/lease/listr_lease.js';
import {type CommandBuilder} from '../types/aliases.js';
import {sleep} from '../core/helpers.js';
import {Duration} from '../core/time/duration.js';

export class AccountCommand extends BaseCommand {
private readonly accountManager: AccountManager;
Expand Down Expand Up @@ -57,7 +59,13 @@ export class AccountCommand extends BaseCommand {
if (!accountInfo || !(accountInfo instanceof AccountInfo))
throw new IllegalArgumentError('An instance of AccountInfo is required');

const newAccountInfo: {accountId: string; balance: number; publicKey: string; privateKey?: string} = {
const newAccountInfo: {
accountId: string;
balance: number;
publicKey: string;
privateKey?: string;
privateKeyRaw?: string;
} = {
accountId: accountInfo.accountId.toString(),
publicKey: accountInfo.key.toString(),
balance: accountInfo.balance.to(HbarUnit.Hbar).toNumber(),
Expand All @@ -66,13 +74,22 @@ export class AccountCommand extends BaseCommand {
if (shouldRetrievePrivateKey) {
const accountKeys = await this.accountManager.getAccountKeysFromSecret(newAccountInfo.accountId, namespace);
newAccountInfo.privateKey = accountKeys.privateKey;

// reconstruct private key to retrieve EVM address if private key is ECDSA type
try {
const privateKey = PrivateKey.fromStringDer(newAccountInfo.privateKey);
newAccountInfo.privateKeyRaw = privateKey.toStringRaw();
} catch (e: Error | any) {

Check warning on line 82 in src/commands/account.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

'e' is defined but never used

Check warning on line 82 in src/commands/account.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Unexpected any. Specify a different type
this.logger.error(`failed to retrieve EVM address for accountId ${newAccountInfo.accountId}`);
}
}

return newAccountInfo;
}

async createNewAccount(ctx: {
config: {
generateEcdsaKey: boolean;
ecdsaPrivateKey?: string;
ed25519PrivateKey?: string;
namespace: string;
Expand All @@ -85,6 +102,8 @@ export class AccountCommand extends BaseCommand {
ctx.privateKey = PrivateKey.fromStringECDSA(ctx.config.ecdsaPrivateKey);
} else if (ctx.config.ed25519PrivateKey) {
ctx.privateKey = PrivateKey.fromStringED25519(ctx.config.ed25519PrivateKey);
} else if (ctx.config.generateEcdsaKey) {
ctx.privateKey = PrivateKey.generateECDSA();
} else {
ctx.privateKey = PrivateKey.generateED25519();
}
Expand All @@ -93,7 +112,7 @@ export class AccountCommand extends BaseCommand {
ctx.config.namespace,
ctx.privateKey,
ctx.config.amount,
ctx.config.ecdsaPrivateKey ? ctx.config.setAlias : false,
ctx.config.ecdsaPrivateKey || ctx.config.generateEcdsaKey ? ctx.config.setAlias : false,
);
}

Expand Down Expand Up @@ -300,6 +319,8 @@ export class AccountCommand extends BaseCommand {
ed25519PrivateKey: string;
namespace: string;
setAlias: boolean;
generateEcdsaKey: boolean;
createAmount: number;
};
privateKey: PrivateKey;
}
Expand All @@ -318,6 +339,8 @@ export class AccountCommand extends BaseCommand {
namespace: self.configManager.getFlag<string>(flags.namespace) as string,
ed25519PrivateKey: self.configManager.getFlag<string>(flags.ed25519PrivateKey) as string,
setAlias: self.configManager.getFlag<boolean>(flags.setAlias) as boolean,
generateEcdsaKey: self.configManager.getFlag<boolean>(flags.generateEcdsaKey) as boolean,
createAmount: self.configManager.getFlag<number>(flags.createAmount) as number,
};

if (!config.amount) {
Expand All @@ -341,10 +364,15 @@ export class AccountCommand extends BaseCommand {
{
title: 'create the new account',
task: async ctx => {
self.accountInfo = await self.createNewAccount(ctx);
const accountInfoCopy = {...self.accountInfo};
delete accountInfoCopy.privateKey;
this.logger.showJSON('new account created', accountInfoCopy);
for (let i = 0; i < ctx.config.createAmount; i++) {
self.accountInfo = await self.createNewAccount(ctx);
const accountInfoCopy = {...self.accountInfo};
delete accountInfoCopy.privateKey;
this.logger.showJSON('new account created', accountInfoCopy);
if (ctx.config.createAmount > 0) {
await sleep(Duration.ofSeconds(1));
}
}
},
},
],
Expand Down Expand Up @@ -553,9 +581,11 @@ export class AccountCommand extends BaseCommand {
flags.setCommandFlags(
y,
flags.amount,
flags.createAmount,
flags.ecdsaPrivateKey,
flags.namespace,
flags.ed25519PrivateKey,
flags.generateEcdsaKey,
flags.setAlias,
),
handler: (argv: any) => {
Expand Down
34 changes: 34 additions & 0 deletions src/commands/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,17 @@ export class Flags {
},
};

static readonly generateEcdsaKey: CommandFlag = {
constName: 'generateEcdsaKey',
name: 'generate-ecdsa-key',
definition: {
describe: 'Generate ECDSA private key for the Hedera account',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};

static readonly ecdsaPrivateKey: CommandFlag = {
constName: 'ecdsaPrivateKey',
name: 'ecdsa-private-key',
Expand Down Expand Up @@ -1137,6 +1148,27 @@ export class Flags {
},
};

static readonly createAmount: CommandFlag = {
constName: 'createAmount',
name: 'create-amount',
definition: {
describe: 'Amount of new account to create',
defaultValue: 1,
type: 'number',
},
prompt: async function promptCreateAmount(task: ListrTaskWrapper<any, any, any>, input: any) {
return await Flags.prompt(
'number',
task,
input,
Flags.createAmount.definition.defaultValue,
'How many account to create? ',
null,
Flags.createAmount.name,
);
},
};

static readonly nodeAlias: CommandFlag = {
constName: 'nodeAlias',
name: 'node-alias',
Expand Down Expand Up @@ -1609,6 +1641,7 @@ export class Flags {
Flags.endpointType,
Flags.soloChartVersion,
Flags.generateGossipKeys,
Flags.generateEcdsaKey,
Flags.generateTlsKeys,
Flags.gossipEndpoints,
Flags.gossipPrivateKey,
Expand All @@ -1623,6 +1656,7 @@ export class Flags {
Flags.namespace,
Flags.newAccountNumber,
Flags.newAdminKey,
Flags.createAmount,
Flags.nodeAlias,
Flags.nodeAliasesUnparsed,
Flags.operatorId,
Expand Down

0 comments on commit 79410f0

Please sign in to comment.