Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CUDOS-2220 refactor test node setup #31

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 38 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
on: pull_request
name: Test

env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
name: Clone repository and checkout branch
- uses: actions/setup-node@v2
name: Setup node.js
- name: Clone repository and checkout branch
uses: actions/checkout@v3

- name: Setup node.js
uses: actions/setup-node@v3
with:
node-version: 16
- run: rm -r .npmrc
name: Remove .npmrc for the test run
- run: yarn
name: Install dependencies
- run: yarn test
name: Run tests
cache: yarn

- name: Install dependencies
run: yarn

- name: Get cudos-node target version from .env
uses: falti/[email protected]
id: dotenv
with:
path: src/jest/cudos-node.local.env

- name: Clone cudos-node repository
uses: actions/checkout@v3
with:
repository: CudoVentures/cudos-node
ref: ${{ steps.dotenv.outputs.BINARY_VERSION }}
path: src/jest/cudos-node

- name: Setup go
uses: actions/setup-go@v4
with:
go-version: 1.18
cache-dependency-path: src/jest/cudos-node/go.sum

- name: Install cudos-node binary
run: make install
working-directory: src/jest/cudos-node

- name: Run tests
run: yarn test
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ yarn-debug.log*
yarn-error.log*

# Temporary testing data
tests/**/test-data/
tests/**/cudos-data/
cudos-node

# Dependency directories
node_modules/
Expand Down Expand Up @@ -42,6 +41,3 @@ build/

# package-lock.json
package-lock.json

# yarn.lock
yarn.lock
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ const queryClient = await StargateClient.connect(rpc)

First we need an RPC endpoint, that will be used to connect to the network. Then a `StargateClient` is created by connecting it to the network.

** In this example the client is connecting to a local test network, created using [cudos-blast](https://github.com/CudoVentures/cudos-blast).

```
...

Expand Down
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
globalSetup: '<rootDir>/src/jest/global-setup.ts',
globalTeardown: '<rootDir>/src/jest/global-teardown.ts',
testEnvironment: '<rootDir>/src/jest/cudos-environment.ts',
testMatch: ['<rootDir>/src/**/*.{spec,test}.ts'],
testTimeout: 20000,
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"registry": "https://registry.npmjs.org"
},
"scripts": {
"test": "cd tests && chmod u+x run-tests.sh && ./run-tests.sh",
"test": "jest --runInBand",
"build": "rm -rf ./build && tsc",
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
Expand Down Expand Up @@ -46,6 +46,7 @@
"@babel/preset-typescript": "^7.18.6",
"@types/jest": "^28.1.3",
"babel-jest": "^28.1.1",
"dotenv": "^16.0.3",
"jest": "^28.1.2",
"ts-jest": "^28.0.5",
"ts-loader": "^9.2.6",
Expand Down
19 changes: 10 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export * from './cosmwasm-stargate'
export * from './proto-signing'
export * from './stargate'
export * from './tendermint-rpc'
export * from './crypto'
export * from './amino'
export * from './utils'
export * from './ledgers'
export * from './math'
export * from './cosmwasm-stargate';
export * from './proto-signing';
export * from './stargate';
export * from './tendermint-rpc';
export * from './crypto';
export * from './amino';
export * from './utils';
export * from './ledgers';
export * from './math';
export * from './jest';
20 changes: 20 additions & 0 deletions src/jest/cudos-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import NodeEnvironment from 'jest-environment-node';
import { CudosNode } from './cudos-node';
import { GasPrice } from '@cosmjs/stargate';

export default class CudosEnvironment extends NodeEnvironment {
// setup() is called once before each test file
override async setup() {
await super.setup();
await CudosNode.instance?.start();

this.global.node = CudosNode.instance;
this.global.gasPrice = GasPrice.fromString(CudosNode.instance?.env['GAS_PRICE']!);
}

// teardown() is called once after each test file
override async teardown() {
await super.teardown();
await CudosNode.instance?.reset();
}
}
13 changes: 13 additions & 0 deletions src/jest/cudos-node.local.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
BINARY_VERSION="v1.1.0"
MONIKER="cudos-node-local"
CHAIN_ID="cudos-node-local"
ORCH_ETH_ADDRESS="0x41d0b5762341b0fce6adccf69572c663481c7286"
CUDOS_TOKEN_CONTRACT_ADDRESS="0x28ea52f3ee46cac5a72f72e8b3a387c0291d586d"
DENOM="acudos"
MIN_SELF_DELEGATION="2000000000000000000000000"
VALIDATOR_BALANCE="90000000000000000000000000acudos,1000cudosAdmin"
ORCH_BALANCE="90000000000000000000000000acudos"
VALIDATOR_MNEMONIC="ordinary witness such toddler tag mouse helmet perfect venue eyebrow upgrade rabbit"
ORCH_MNEMONIC="course hurdle stand heart rescue trap upset cousin dish embody business equip"
GAS_PRICE="0acudos"
CUDOS_HOME="/tmp/cudos-local-data"
52 changes: 52 additions & 0 deletions src/jest/cudos-node.local.setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash -e

# set current working directory to cudosjs/src/jest
cd $(dirname $0)

# load environment variables
source ./cudos-node.local.env
# set CUDOS_HOME global env to be used as default --home folder
export CUDOS_HOME=$CUDOS_HOME
# cleanup data folder from previous runs
rm -rf $CUDOS_HOME

# the tests require cudos-noded binary
# our github actions workflow does this
# otherwise we need to manually clone the repo and build from source
if [[ $GITHUB_ACTIONS != true ]]; then
rm -rf cudos-node
git clone --branch=$BINARY_VERSION https://github.com/CudoVentures/cudos-node.git &> /dev/null || true
cd cudos-node
make install &> /dev/null
fi

# initialize data folder with config and genesis
cudos-noded init $MONIKER --chain-id=$CHAIN_ID &> /dev/null

# create self-delegate account
echo $VALIDATOR_MNEMONIC | cudos-noded keys add validator --keyring-backend=test --recover
# get self-delegate address
validatorAddress=$(cudos-noded keys show validator -a --keyring-backend=test)
# fund self-delegate and add to genesis
cudos-noded add-genesis-account $validatorAddress ${VALIDATOR_BALANCE}

# create orchestrator account
echo $ORCH_MNEMONIC | cudos-noded keys add orch --keyring-backend=test --recover
# get orchestrator address
orchAddress=$(cudos-noded keys show orch -a --keyring-backend=test)
# fund orchestrator and add to genesis
cudos-noded add-genesis-account $orchAddress ${ORCH_BALANCE}

# create validator account
cudos-noded gentx validator ${MIN_SELF_DELEGATION}${DENOM} ${ORCH_ETH_ADDRESS} ${orchAddress} --min-self-delegation=$MIN_SELF_DELEGATION --chain-id=$CHAIN_ID --keyring-backend=test &> /dev/null
# add to genesis
cudos-noded collect-gentxs &> /dev/null

# set minimum-gas-prices
sed -i.bak "s/minimum-gas-prices = \"\"/minimum-gas-prices = \"$GAS_PRICE\"/" $CUDOS_HOME/config/app.toml
# set denom name
sed -i.bak "s/\"stake\"/\"$DENOM\"/g" $CUDOS_HOME/config/genesis.json
# set validator self-delegate address
sed -i.bak "s/\"static_val_cosmos_addrs\"\: \[\]/\"static_val_cosmos_addrs\": [\"$validatorAddress\"]/" $CUDOS_HOME/config/genesis.json
# set token contract address on ethereum
sed -i.bak "s/\"erc20_to_denoms\"\: \[\]/\"erc20_to_denoms\": [{\"erc20\":\"$CUDOS_TOKEN_CONTRACT_ADDRESS\",\"denom\":\"$DENOM\"}]/" $CUDOS_HOME/config/genesis.json
118 changes: 118 additions & 0 deletions src/jest/cudos-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
DirectSecp256k1HdWallet,
OfflineSigner,
SigningStargateClient,
StargateClient,
Tendermint34Client,
} from '..';
import { execSync } from 'child_process';
import { setTimeout } from 'timers/promises';
import dotenv from 'dotenv';

const DEFAULT_RPC = 'http://localhost:26657';

// CudosAccount extends CudosSigningStargateClient by adding address helper field for test purposes
class CudosAccount extends SigningStargateClient {
private _address: string;

public static fromMnemonic = async (mnemonic: string, rpcUrl: string) => {
const tmClient = await Tendermint34Client.connect(rpcUrl);
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic);
const address = (await signer.getAccounts())[0].address;
return new CudosAccount(tmClient, signer, address);
};

private constructor(tmClient: Tendermint34Client, signer: OfflineSigner, address: string) {
super(tmClient, signer, {});
this._address = address;
}

public get address() {
return this._address;
}
}

export class CudosNode {
// we use singleton pattern to make sure we only have one node instance
private static _instance: CudosNode | undefined;
private started: boolean = false;
private _queryClient: StargateClient | undefined;
private _validator: CudosAccount | undefined;
private _orchestrator: CudosAccount | undefined;
public readonly env: dotenv.DotenvParseOutput;

// init is called once before jest starts executing the tests
public static init = () => {
if (CudosNode._instance) {
throw new Error('Node is already instantiated.');
}

execSync(`${__dirname}/cudos-node.local.setup.sh`);

// create singleton instance once the node is initializeed
CudosNode._instance = new CudosNode();
};

private constructor() {
this.env = dotenv.config({ path: `${__dirname}/cudos-node.local.env` }).parsed!;
}

public start = async () => {
if (this.started) {
throw new Error('Node is already started.');
}

execSync(`cudos-noded start --log_level=fatal &`, { stdio: 'inherit' });

// wait 8sec for node to start producing blocks then verify height
await setTimeout(8000);
const queryClient = await StargateClient.connect(DEFAULT_RPC);
const height = await queryClient.getHeight();
if (height !== 1) {
throw new Error(`error while starting local cudos node, expected height 1, received ${height}`);
}

this.started = true;

if (!this._validator || !this._orchestrator || !this._queryClient) {
this._validator = await CudosAccount.fromMnemonic(this.env['VALIDATOR_MNEMONIC'], DEFAULT_RPC);
this._orchestrator = await CudosAccount.fromMnemonic(this.env['ORCH_MNEMONIC'], DEFAULT_RPC);
this._queryClient = queryClient;
}
};

public reset = () => {
execSync('pkill cudos-noded || true');
execSync(`cudos-noded unsafe-reset-all --log_level=error`);
this.started = false;
};

// cleanup is called once after jest finishes test execution
public cleanup = () => {
if (this.started) {
this.reset();
}

execSync(`rm -rf ${__dirname}/cudos-node`);
execSync(`rm -rf ${this.env['CUDOS_HOME']}`);

// delete singleton instance after tests are completed
delete CudosNode._instance;
};

public get queryClient() {
return this._queryClient;
}

public get validator() {
return this._validator;
}

public get orchestrator() {
return this._orchestrator;
}

public static get instance() {
return CudosNode._instance;
}
}
9 changes: 9 additions & 0 deletions src/jest/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CudosNode } from './cudos-node';

// jestGlobalSetup is called once before executing tests
export const jestGlobalSetup = () => {
console.log('Setup local cudos node...');
CudosNode.init();
};

module.exports = jestGlobalSetup;
9 changes: 9 additions & 0 deletions src/jest/global-teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CudosNode } from "./cudos-node";

// jestGlobalTeardown is called once after executing tests
export const jestGlobalTeardown = () => {
console.log('Cleanup cudos node...');
CudosNode.instance?.cleanup();
};

module.exports = jestGlobalTeardown;
8 changes: 8 additions & 0 deletions src/jest/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Coin, GasPrice } from '@cosmjs/stargate';
import { CudosNode } from './cudos-node';

// types for globalThis
export declare global {
var node: CudosNode | undefined;
var gasPrice: GasPrice;
}
4 changes: 4 additions & 0 deletions src/jest/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './cudos-environment';
export * from './cudos-node';
export * from './global-setup';
export * from './global-teardown';
20 changes: 20 additions & 0 deletions src/stargate/modules/addressbook/addressbook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
describe('addressbook', () => {
const alice = globalThis.node?.validator!;
const queryClient = globalThis.node?.queryClient!;
const gas = globalThis.gasPrice;
const network = 'BTC';
const label = '1@denom';

jest.setTimeout(40000);

// positive test case
test('general flow', async () => {
await alice.addressbookCreateAddress(alice.address, network, label, 'addr1', gas);
await alice.addressbookUpdateAddress(alice.address, network, label, 'addr2', gas);
const value = await queryClient.addressbookModule.getAddress(alice.address, network, label);
expect(value.address).toBeDefined();
expect(value.address!.value).toEqual('addr2');
await alice.addressbookDeleteAddress(alice.address, network, label, gas);
await queryClient.addressbookModule.getAllAddresses();
});
});
Loading