Skip to content

Commit

Permalink
Problem: ibc solo machine integration not tested (fix #613) #757
Browse files Browse the repository at this point in the history
Solution: solo machine binary injected via Nix environment + a custom test set for a solo machine
  • Loading branch information
linfeng-crypto authored May 17, 2022
1 parent ef0bb6d commit 3fdc974
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 2 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,28 @@ jobs:
path: debug_files_ledger.tar.gz
if-no-files-found: ignore

test-solomachine:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: cachix/install-nix-action@v14
- uses: cachix/cachix-action@v10
with:
name: crypto-com
- name: Run integration tests
run: make nix-integration-test-solomachine
- name: Tar debug files
if: failure()
run: tar cfz debug_files_solomachine.tar.gz -C /tmp/pytest-of-runner .
- uses: actions/upload-artifact@v2
if: failure()
with:
name: debug_files_solomachine
path: debug_files_solomachine.tar.gz
if-no-files-found: ignore

test-slow:
runs-on: ubuntu-latest
steps:
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ make-proto:
nix-integration-test: check-network make-proto
nix shell -f ./default.nix run-integration-tests --extra-experimental-features nix-command --command run-integration-tests

nix-integration-test-solomachine: check-network
nix shell -f ./default.nix run-integration-tests --extra-experimental-features nix-command --command run-integration-tests "pytest -v -m solomachine"

nix-integration-test-upgrade: check-network
nix shell -f ./default.nix run-integration-tests --extra-experimental-features nix-command --command run-integration-tests "pytest -v -m upgrade"

Expand Down
10 changes: 8 additions & 2 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ rec {

chain-maind-testnet = build-chain-maind { network = "testnet"; };

solomachine = pkgs.callPackage ./integration_tests/install_solo_machine.nix { };

# for testing and dev
chain-maind-zemu = build-chain-maind { ledger_zemu = true; };

Expand Down Expand Up @@ -110,6 +112,7 @@ rec {
"^integration_tests/configs$"
"^integration_tests/configs/.*"
"^integration_tests/upgrade-test.nix$"
"^integration_tests/install_solo_machine.nix$"
"^integration_tests/upgrade-test.patch$"
"^nix$"
"^nix/.*"
Expand All @@ -124,20 +127,23 @@ rec {
lint-env
chain-maind-zemu
chain-maind-zemu.instrumented
solomachine
] ++ common-env;
};


# main entrypoint script to run integration tests
run-integration-tests = pkgs.writeShellScriptBin "run-integration-tests" ''
set -e
export PATH=${ci-env}/bin:$PATH
export TESTS=${tests_src}/integration_tests
export PYTHONPATH=$PWD/pystarport/proto_python/:$PYTHONPATH
export CHAIN_MAIND="${chain-maind}/bin/chain-maind"
export SOLO_MACHINE_HOME="${solomachine}/solomachine"
# check argument exists, then use it, otherwise use default
if [ -z $1 ]
then
pytest -v -m 'not upgrade and not ledger and not slow and not ibc and not byzantine and not gov and not grpc' $TESTS
pytest -v -m 'not upgrade and not ledger and not slow and not ibc and not byzantine and not gov and not grpc and not solomachine' $TESTS
else
$1 $TESTS
fi
Expand All @@ -153,7 +159,7 @@ rec {
# check argument exists, then use it, otherwise use default
if [ -z $1 ]
then
pytest -v -m 'not upgrade and not ledger and not slow and not ibc and not byzantine and not gov and not grpc' $TESTS
pytest -v -m 'not upgrade and not ledger and not slow and not ibc and not byzantine and not gov and not grpc not solomachine' $TESTS
else
$1 $TESTS
fi
Expand Down
60 changes: 60 additions & 0 deletions integration_tests/configs/solo_machine.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
devnet-solomachine:
validators:
- coins: 1000cro
staked: 1000cro
accounts:
- name: solo-signer
mnemonic: 'awesome there minute cash write immune tag reopen price congress trouble reunion south wisdom donate credit below leave wisdom eagle sail siege rice train'
coins: 1500cro
consensus:
timeout_commit: "5s"
genesis:
consensus_params:
block:
max_bytes: "1048576"
max_gas: "81500000"
time_iota_ms: "1000"
evidence:
max_age_num_blocks: "403200"
max_age_duration: "2419200000000000"
max_bytes: "150000"
app_state:
distribution:
params:
community_tax: "0"
base_proposer_reward: "0"
bonus_proposer_reward: "0"
gov:
deposit_params:
max_deposit_period: "21600000000000ns"
min_deposit: [{"denom":"basecro","amount":"2000000000000"}]
voting_params:
voting_period: "21600000000000ns"
mint:
minter:
inflation: "0.000000000000000000"
params:
blocks_per_year: "6311520"
mint_denom: "basecro"
inflation_rate_change: "0"
inflation_max: "0"
inflation_min: "0"
goal_bonded: "1"
slashing:
params:
downtime_jail_duration: "3600s"
min_signed_per_window: "0.5"
signed_blocks_window: "5000"
slash_fraction_double_sign: "0"
slash_fraction_downtime: "0"
staking:
params:
bond_denom: "basecro"
historical_entries: "10000"
max_entries: "7"
max_validators: "50"
unbonding_time: "2419200000000000ns"
transfer:
params:
receive_enabled: true
send_enabled: true
31 changes: 31 additions & 0 deletions integration_tests/install_solo_machine.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{ pkgs ? import <nixpkgs> { } }:
let
version = "v0.1.2";
srcUrl = {
x86_64-linux = {
url =
"https://github.com/crypto-com/ibc-solo-machine/releases/download/${version}/ubuntu-latest-${version}.tar.gz";
sha256 = "sha256-GEfHyUKvq69RAGWv29PAG3pFlBraXXhdkTcG045dePw=";
};
x86_64-darwin = {
url =
"https://github.com/crypto-com/ibc-solo-machine/releases/download/${version}/macos-latest-${version}.tar.gz";
sha256 = "sha256-zx4342stMYzgQDXAKwnZKSfdLynGIApOFKZ+CjRCyaE=";
};
}.${pkgs.stdenv.system} or (throw
"Unsupported system: ${pkgs.stdenv.system}");
in
pkgs.stdenv.mkDerivation {
name = "solomachine";
inherit version;
src = pkgs.fetchurl srcUrl;
sourceRoot = ".";
installPhase = ''
echo "installing solomachine ..."
echo $out
mkdir -p $out/solomachine
install -m 755 -v -D * $out/solomachine
echo `env`
'';
meta = with pkgs.lib; { platforms = with platforms; linux ++ darwin; };
}
202 changes: 202 additions & 0 deletions integration_tests/test_solo_machine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import json
import os
import platform
from pathlib import Path

import pytest
import requests
import yaml
from pystarport import ports
from pystarport.utils import interact

from .utils import cluster_fixture, wait_for_block, wait_for_port

pytestmark = pytest.mark.solomachine


@pytest.fixture(scope="module")
def cluster(worker_index, pytestconfig, tmp_path_factory):
"override cluster fixture for this test module"
try:
yield from cluster_fixture(
Path(__file__).parent / "configs/solo_machine.yaml",
worker_index,
tmp_path_factory.mktemp("data"),
)
finally:
pass


def parse_output(output, return_json=True):
s = output.decode("utf-8")
s = s.replace("\u001b[0m", "")
print(s)
if return_json:
data = json.loads(s)
return data
else:
return s


CRO_DECIMALS = 10 ** 8
SOLO_HD_PATH = "m/44'/394'/0'/0/0"
SOLO_ACCOUNT_PREFIX = "cro"
SOLO_ADDRESS_ALGO = "secp256k1"
SOLO_FEE_DENOM = "basecro"
SOLO_DENOM = "solotoken"


class SoloMachine(object):
def __init__(
self, temp_path, mnemonic, base_port=26650, chain_id="devnet-solomachine"
):
self.chain_id = chain_id
self.grpc_port = ports.grpc_port(base_port)
self.rpc_port = ports.rpc_port(base_port)
self.solomachine_home = os.path.join(os.environ["SOLO_MACHINE_HOME"])
self.bin_file = os.path.join(self.solomachine_home, "solo-machine")
self.mnemonic = mnemonic

os_platform = platform.system()
if os_platform == "Darwin":
self.sign_file = os.path.join(
self.solomachine_home, "libmnemonic_signer.dylib"
)
else:
self.sign_file = os.path.join(
self.solomachine_home, "libmnemonic_signer.so"
)
self.trusted_height = None
self.trusted_hash = None
self.db_path = f'sqlite://{temp_path.join("solo-machine.db")}'

def get_chain_info(self):
result = requests.get(f"http://127.0.0.1:{self.rpc_port}/block").json()
self.trusted_hash = result["result"]["block_id"]["hash"]
self.trusted_height = result["result"]["block"]["header"]["height"]

def set_env(self):
os.environ["SOLO_DB_URI"] = self.db_path
os.environ["SOLO_SIGNER"] = self.sign_file
os.environ["SOLO_MNEMONIC"] = self.mnemonic
os.environ["SOLO_HD_PATH"] = SOLO_HD_PATH
os.environ["SOLO_ACCOUNT_PREFIX"] = SOLO_ACCOUNT_PREFIX
os.environ["SOLO_ADDRESS_ALGO"] = SOLO_ADDRESS_ALGO
os.environ["SOLO_FEE_DENOM"] = SOLO_FEE_DENOM
os.environ["SOLO_FEE_AMOUNT"] = "1000"
os.environ["SOLO_GAS_LIMIT"] = "300000"
os.environ["SOLO_GRPC_ADDRESS"] = f"http://0.0.0.0:{self.grpc_port}"
os.environ["SOLO_RPC_ADDRESS"] = f"http://0.0.0.0:{self.rpc_port}"
os.environ["SOLO_TRUSTED_HASH"] = self.trusted_hash
os.environ["SOLO_TRUSTED_HEIGHT"] = self.trusted_height

def prepare(self):
self.get_chain_info()
self.set_env()

def run_sub_cmd(self, sub_cmd, json_output=True):
if json_output:
cmd = f"{self.bin_file} --output json {sub_cmd}"
else:
cmd = f"{self.bin_file} {sub_cmd}"
output = interact(cmd)
data = parse_output(output, json_output)
return data

def init(self):
sub_cmd = "init"
return self.run_sub_cmd(sub_cmd)

def add_chain(self):
sub_cmd = "chain add"
return self.run_sub_cmd(sub_cmd)

def connect_chain(self):
sub_cmd = f"ibc connect {self.chain_id}"
result = self.run_sub_cmd(sub_cmd, False)
if "error" in result:
raise Exception(result)

def get_balance(self):
sub_cmd = f"chain balance {self.chain_id} {SOLO_DENOM}"
return self.run_sub_cmd(sub_cmd)["data"]["balance"]

def mint(self, amount=20):
sub_cmd = f"ibc mint {self.chain_id} {amount} {SOLO_DENOM}"
return self.run_sub_cmd(sub_cmd)

def burn(self, amount=10):
sub_cmd = f"ibc burn {self.chain_id} {amount} {SOLO_DENOM}"
return self.run_sub_cmd(sub_cmd)


def get_balance(cluster, addr):
raw = cluster.cosmos_cli(0).raw
node = cluster.node_rpc(0)
coin = json.loads(raw("query", "bank", "balances", addr, output="json", node=node))[
"balances"
]
if len(coin) == 0:
return None
return coin[0]


def get_mnemonic(cli):
config = yaml.safe_load(open(Path(__file__).parent / "configs/solo_machine.yaml"))
return config[cli.chain_id]["accounts"][0]["mnemonic"]


def test_solo_machine(cluster, tmpdir_factory):
"""
check solo machine
"""
cli = cluster
wait_for_block(cli, 1)

# get the chain balance
chain_solo_addr = cluster.address("solo-signer")
chain_balance_1 = cluster.balance(chain_solo_addr)
assert chain_balance_1 == 1500 * CRO_DECIMALS

base_port = cli.base_port(0)
wait_for_port(ports.grpc_port(base_port))
tmp_path = tmpdir_factory.mktemp("db")
mnemonic = get_mnemonic(cli)
solo_machine = SoloMachine(tmp_path, mnemonic, base_port=base_port)
solo_machine.prepare()
data = solo_machine.init()
assert data["result"] == "success"
data = solo_machine.add_chain()
assert data["result"] == "success"
wait_for_block(cli, 2)
solo_machine.connect_chain()
wait_for_block(cli, 2)
balance_0 = int(solo_machine.get_balance())
assert balance_0 == 0

# mint
data = solo_machine.mint(20)
assert data["result"] == "success"
assert int(data["data"]["amount"], 16) == 20
wait_for_block(cli, 2)
balance_1 = int(solo_machine.get_balance())
assert balance_1 == 20

# check the chain balance

chain_balance_2 = get_balance(cli, chain_solo_addr)
assert int(chain_balance_2["amount"]) < chain_balance_1
assert chain_balance_2["denom"] == SOLO_FEE_DENOM

# burn
data = solo_machine.burn(10)
assert data["result"] == "success"
assert int(data["data"]["amount"], 16) == 10
wait_for_block(cli, 2)
balance_2 = int(solo_machine.get_balance())
assert balance_2 == balance_1 - 10

# check the chain balance
chain_balance_3 = get_balance(cli, chain_solo_addr)
assert int(chain_balance_3["amount"]) < int(chain_balance_2["amount"])
assert chain_balance_3["denom"] == SOLO_FEE_DENOM

0 comments on commit 3fdc974

Please sign in to comment.