Skip to content

Commit 79410f0

Browse files
feat: add solo smoke test to test flow (#905)
Signed-off-by: Jeffrey Tang <[email protected]> Signed-off-by: Jeromy Cannon <[email protected]> Co-authored-by: Jeromy Cannon <[email protected]>
1 parent b378937 commit 79410f0

File tree

5 files changed

+269
-6
lines changed

5 files changed

+269
-6
lines changed

.github/workflows/flow-task-test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,6 @@ jobs:
7373
- name: Run Example Task File Test
7474
run: |
7575
task default-with-relay
76+
sleep 10
77+
.github/workflows/script/solo_smoke_test.sh
7678
task clean
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/bin/bash
2+
set -eo pipefail
3+
4+
#
5+
# This script should be called after solo has been deployed with mirror node and relay node deployed,
6+
# and should be called from the root of the solo repository
7+
#
8+
# This uses solo account creation function to repeatedly generate background transactions
9+
# Then run smart contract test, and also javascript sdk sample test to interact with solo network
10+
#
11+
12+
function_name=""
13+
14+
function enable_port_forward ()
15+
{
16+
kubectl port-forward -n solo-e2e svc/haproxy-node1-svc 50211:50211 > /dev/null 2>&1 &
17+
kubectl port-forward -n solo-e2e svc/hedera-explorer 8080:80 > /dev/null 2>&1 &
18+
kubectl port-forward -n solo-e2e svc/relay-node1-hedera-json-rpc-relay 7546:7546 > /dev/null 2>&1 &
19+
kubectl port-forward -n solo-e2e svc/mirror-grpc 5600:5600 > /dev/null 2>&1 &
20+
}
21+
22+
function clone_smart_contract_repo ()
23+
{
24+
echo "Clone hedera-smart-contracts"
25+
if [ -d "hedera-smart-contracts" ]; then
26+
echo "Directory hedera-smart-contracts exists."
27+
else
28+
echo "Directory hedera-smart-contracts does not exist."
29+
git clone https://github.com/hashgraph/hedera-smart-contracts --branch only-erc20-tests
30+
fi
31+
}
32+
33+
function setup_smart_contract_test ()
34+
{
35+
echo "Setup smart contract test"
36+
cd hedera-smart-contracts
37+
38+
echo "Remove previous .env file"
39+
rm -f .env
40+
41+
npm install
42+
npx hardhat compile || return 1:
43+
44+
echo "Build .env file"
45+
46+
echo "PRIVATE_KEYS=\"$CONTRACT_TEST_KEYS\"" > .env
47+
echo "RETRY_DELAY=5000 # ms" >> .env
48+
echo "MAX_RETRY=5" >> .env
49+
cat .env
50+
cd -
51+
}
52+
53+
function start_background_transactions ()
54+
{
55+
echo "Start background transaction"
56+
# generate accounts as background traffic for two minutes
57+
# so record stream files can be kept pushing to mirror node
58+
cd solo
59+
npm run solo-test -- account create -n solo-e2e --create-amount 15 > /dev/null 2>&1 &
60+
cd -
61+
}
62+
63+
function start_contract_test ()
64+
{
65+
cd hedera-smart-contracts
66+
echo "Wait a few seconds for background transactions to start"
67+
sleep 5
68+
echo "Run smart contract test"
69+
npm run hh:test
70+
result=$?
71+
72+
cd -
73+
return $result
74+
}
75+
76+
function create_test_account ()
77+
{
78+
echo "Create test account with solo network"
79+
cd solo
80+
81+
# create new account and extract account id
82+
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
83+
export OPERATOR_ID=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
84+
echo "OPERATOR_ID=${OPERATOR_ID}"
85+
rm test.log
86+
87+
# get private key of the account
88+
npm run solo-test -- account get -n solo-e2e --account-id ${OPERATOR_ID} --private-key > test.log
89+
export OPERATOR_KEY=$(grep "privateKey" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
90+
export CONTRACT_TEST_KEY_ONE=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
91+
echo "CONTRACT_TEST_KEY_ONE=${CONTRACT_TEST_KEY_ONE}"
92+
rm test.log
93+
94+
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
95+
export SECOND_KEY=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
96+
npm run solo-test -- account get -n solo-e2e --account-id ${SECOND_KEY} --private-key > test.log
97+
export CONTRACT_TEST_KEY_TWO=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
98+
echo "CONTRACT_TEST_KEY_TWO=${CONTRACT_TEST_KEY_TWO}"
99+
rm test.log
100+
101+
export CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEY_ONE},$'\n'${CONTRACT_TEST_KEY_TWO}
102+
export HEDERA_NETWORK="local-node"
103+
104+
echo "OPERATOR_KEY=${OPERATOR_KEY}"
105+
echo "HEDERA_NETWORK=${HEDERA_NETWORK}"
106+
echo "CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEYS}"
107+
108+
cd -
109+
}
110+
111+
function start_sdk_test ()
112+
{
113+
cd solo
114+
node examples/create-topic.js
115+
result=$?
116+
117+
cd -
118+
return $result
119+
}
120+
121+
echo "Restart port-forward"
122+
task helper:clean:port-forward
123+
enable_port_forward
124+
125+
126+
echo "Change to parent directory"
127+
cd ../
128+
create_test_account
129+
clone_smart_contract_repo
130+
setup_smart_contract_test
131+
start_background_transactions
132+
start_contract_test
133+
start_sdk_test
134+
echo "Sleep a while to wait background transactions to finish"
135+
sleep 30

examples/create-topic.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright (C) 2024 Hedera Hashgraph, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the ""License"");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an ""AS IS"" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
import {Wallet, LocalProvider, TopicCreateTransaction, TopicMessageSubmitTransaction} from '@hashgraph/sdk';
18+
19+
import dotenv from 'dotenv';
20+
21+
dotenv.config();
22+
23+
async function main() {
24+
if (process.env.OPERATOR_ID === null || process.env.OPERATOR_KEY === null || process.env.HEDERA_NETWORK === null) {
25+
throw new Error('Environment variables OPERATOR_ID, HEDERA_NETWORK, and OPERATOR_KEY are required.');
26+
}
27+
28+
console.log(`Hedera network = ${process.env.HEDERA_NETWORK}`);
29+
const provider = new LocalProvider();
30+
31+
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);
32+
33+
try {
34+
console.log('before create topic');
35+
// create topic
36+
let transaction = await new TopicCreateTransaction().freezeWithSigner(wallet);
37+
transaction = await transaction.signWithSigner(wallet);
38+
console.log('after sign transaction');
39+
const createResponse = await transaction.executeWithSigner(wallet);
40+
const createReceipt = await createResponse.getReceiptWithSigner(wallet);
41+
42+
console.log(`topic id = ${createReceipt.topicId.toString()}`);
43+
44+
// send one message
45+
let topicMessageSubmitTransaction = await new TopicMessageSubmitTransaction({
46+
topicId: createReceipt.topicId,
47+
message: 'Hello World',
48+
}).freezeWithSigner(wallet);
49+
topicMessageSubmitTransaction = await topicMessageSubmitTransaction.signWithSigner(wallet);
50+
const sendResponse = await topicMessageSubmitTransaction.executeWithSigner(wallet);
51+
52+
const sendReceipt = await sendResponse.getReceiptWithSigner(wallet);
53+
54+
console.log(`topic sequence number = ${sendReceipt.topicSequenceNumber.toString()}`);
55+
} catch (error) {
56+
console.error(error);
57+
}
58+
59+
provider.close();
60+
}
61+
62+
void main();

src/commands/account.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {FREEZE_ADMIN_ACCOUNT} from '../core/constants.js';
2626
import {type Opts} from '../types/command_types.js';
2727
import {ListrLease} from '../core/lease/listr_lease.js';
2828
import {type CommandBuilder} from '../types/aliases.js';
29+
import {sleep} from '../core/helpers.js';
30+
import {Duration} from '../core/time/duration.js';
2931

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

60-
const newAccountInfo: {accountId: string; balance: number; publicKey: string; privateKey?: string} = {
62+
const newAccountInfo: {
63+
accountId: string;
64+
balance: number;
65+
publicKey: string;
66+
privateKey?: string;
67+
privateKeyRaw?: string;
68+
} = {
6169
accountId: accountInfo.accountId.toString(),
6270
publicKey: accountInfo.key.toString(),
6371
balance: accountInfo.balance.to(HbarUnit.Hbar).toNumber(),
@@ -66,13 +74,22 @@ export class AccountCommand extends BaseCommand {
6674
if (shouldRetrievePrivateKey) {
6775
const accountKeys = await this.accountManager.getAccountKeysFromSecret(newAccountInfo.accountId, namespace);
6876
newAccountInfo.privateKey = accountKeys.privateKey;
77+
78+
// reconstruct private key to retrieve EVM address if private key is ECDSA type
79+
try {
80+
const privateKey = PrivateKey.fromStringDer(newAccountInfo.privateKey);
81+
newAccountInfo.privateKeyRaw = privateKey.toStringRaw();
82+
} catch (e: Error | any) {
83+
this.logger.error(`failed to retrieve EVM address for accountId ${newAccountInfo.accountId}`);
84+
}
6985
}
7086

7187
return newAccountInfo;
7288
}
7389

7490
async createNewAccount(ctx: {
7591
config: {
92+
generateEcdsaKey: boolean;
7693
ecdsaPrivateKey?: string;
7794
ed25519PrivateKey?: string;
7895
namespace: string;
@@ -85,6 +102,8 @@ export class AccountCommand extends BaseCommand {
85102
ctx.privateKey = PrivateKey.fromStringECDSA(ctx.config.ecdsaPrivateKey);
86103
} else if (ctx.config.ed25519PrivateKey) {
87104
ctx.privateKey = PrivateKey.fromStringED25519(ctx.config.ed25519PrivateKey);
105+
} else if (ctx.config.generateEcdsaKey) {
106+
ctx.privateKey = PrivateKey.generateECDSA();
88107
} else {
89108
ctx.privateKey = PrivateKey.generateED25519();
90109
}
@@ -93,7 +112,7 @@ export class AccountCommand extends BaseCommand {
93112
ctx.config.namespace,
94113
ctx.privateKey,
95114
ctx.config.amount,
96-
ctx.config.ecdsaPrivateKey ? ctx.config.setAlias : false,
115+
ctx.config.ecdsaPrivateKey || ctx.config.generateEcdsaKey ? ctx.config.setAlias : false,
97116
);
98117
}
99118

@@ -300,6 +319,8 @@ export class AccountCommand extends BaseCommand {
300319
ed25519PrivateKey: string;
301320
namespace: string;
302321
setAlias: boolean;
322+
generateEcdsaKey: boolean;
323+
createAmount: number;
303324
};
304325
privateKey: PrivateKey;
305326
}
@@ -318,6 +339,8 @@ export class AccountCommand extends BaseCommand {
318339
namespace: self.configManager.getFlag<string>(flags.namespace) as string,
319340
ed25519PrivateKey: self.configManager.getFlag<string>(flags.ed25519PrivateKey) as string,
320341
setAlias: self.configManager.getFlag<boolean>(flags.setAlias) as boolean,
342+
generateEcdsaKey: self.configManager.getFlag<boolean>(flags.generateEcdsaKey) as boolean,
343+
createAmount: self.configManager.getFlag<number>(flags.createAmount) as number,
321344
};
322345

323346
if (!config.amount) {
@@ -341,10 +364,15 @@ export class AccountCommand extends BaseCommand {
341364
{
342365
title: 'create the new account',
343366
task: async ctx => {
344-
self.accountInfo = await self.createNewAccount(ctx);
345-
const accountInfoCopy = {...self.accountInfo};
346-
delete accountInfoCopy.privateKey;
347-
this.logger.showJSON('new account created', accountInfoCopy);
367+
for (let i = 0; i < ctx.config.createAmount; i++) {
368+
self.accountInfo = await self.createNewAccount(ctx);
369+
const accountInfoCopy = {...self.accountInfo};
370+
delete accountInfoCopy.privateKey;
371+
this.logger.showJSON('new account created', accountInfoCopy);
372+
if (ctx.config.createAmount > 0) {
373+
await sleep(Duration.ofSeconds(1));
374+
}
375+
}
348376
},
349377
},
350378
],
@@ -553,9 +581,11 @@ export class AccountCommand extends BaseCommand {
553581
flags.setCommandFlags(
554582
y,
555583
flags.amount,
584+
flags.createAmount,
556585
flags.ecdsaPrivateKey,
557586
flags.namespace,
558587
flags.ed25519PrivateKey,
588+
flags.generateEcdsaKey,
559589
flags.setAlias,
560590
),
561591
handler: (argv: any) => {

src/commands/flags.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,17 @@ export class Flags {
10651065
},
10661066
};
10671067

1068+
static readonly generateEcdsaKey: CommandFlag = {
1069+
constName: 'generateEcdsaKey',
1070+
name: 'generate-ecdsa-key',
1071+
definition: {
1072+
describe: 'Generate ECDSA private key for the Hedera account',
1073+
defaultValue: false,
1074+
type: 'boolean',
1075+
},
1076+
prompt: undefined,
1077+
};
1078+
10681079
static readonly ecdsaPrivateKey: CommandFlag = {
10691080
constName: 'ecdsaPrivateKey',
10701081
name: 'ecdsa-private-key',
@@ -1137,6 +1148,27 @@ export class Flags {
11371148
},
11381149
};
11391150

1151+
static readonly createAmount: CommandFlag = {
1152+
constName: 'createAmount',
1153+
name: 'create-amount',
1154+
definition: {
1155+
describe: 'Amount of new account to create',
1156+
defaultValue: 1,
1157+
type: 'number',
1158+
},
1159+
prompt: async function promptCreateAmount(task: ListrTaskWrapper<any, any, any>, input: any) {
1160+
return await Flags.prompt(
1161+
'number',
1162+
task,
1163+
input,
1164+
Flags.createAmount.definition.defaultValue,
1165+
'How many account to create? ',
1166+
null,
1167+
Flags.createAmount.name,
1168+
);
1169+
},
1170+
};
1171+
11401172
static readonly nodeAlias: CommandFlag = {
11411173
constName: 'nodeAlias',
11421174
name: 'node-alias',
@@ -1609,6 +1641,7 @@ export class Flags {
16091641
Flags.endpointType,
16101642
Flags.soloChartVersion,
16111643
Flags.generateGossipKeys,
1644+
Flags.generateEcdsaKey,
16121645
Flags.generateTlsKeys,
16131646
Flags.gossipEndpoints,
16141647
Flags.gossipPrivateKey,
@@ -1623,6 +1656,7 @@ export class Flags {
16231656
Flags.namespace,
16241657
Flags.newAccountNumber,
16251658
Flags.newAdminKey,
1659+
Flags.createAmount,
16261660
Flags.nodeAlias,
16271661
Flags.nodeAliasesUnparsed,
16281662
Flags.operatorId,

0 commit comments

Comments
 (0)