From 3e22a6a0275d56da609183a1f4647ecd95f84686 Mon Sep 17 00:00:00 2001 From: Bo QIU <35757521+boqiu@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:45:02 +0800 Subject: [PATCH] Supports evmos as blockchain fullnode in python tests (#40) * refine blockchain type * Supports evmos * cp evmos to code dir * improve timestamp --- tests/config/evmos-init-genesis.sh | 63 +++++++++++++++++++ tests/config/node_config.py | 3 + tests/mine_test.py | 1 - tests/mine_with_market_test.py | 1 - tests/random_test.py | 3 +- tests/revert_test.py | 2 +- tests/test_framework/blockchain_node.py | 7 +-- tests/test_framework/evmos_node.py | 82 +++++++++++++++++++++++++ tests/test_framework/test_framework.py | 49 +++++++++++++-- tests/test_framework/zgs_node.py | 2 +- tests/utility/run_all.py | 68 +++++++++++++------- tests/utility/utils.py | 2 + 12 files changed, 245 insertions(+), 38 deletions(-) create mode 100644 tests/config/evmos-init-genesis.sh create mode 100644 tests/test_framework/evmos_node.py diff --git a/tests/config/evmos-init-genesis.sh b/tests/config/evmos-init-genesis.sh new file mode 100644 index 0000000..4a39518 --- /dev/null +++ b/tests/config/evmos-init-genesis.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +set -e + +EVMOSD=$(cd $(dirname ${BASH_SOURCE[0]})/../tmp; pwd)/evmosd +ROOT_DIR=${1:-.} +NUM_NODES=${2:-3} +P2P_PORT_START=${3:-26656} +CHAIN_ID=evmospy_9000-777 + +# install jq if not unavailable +jq --version >/dev/null 2>&1 || sudo snap install jq -y + +mkdir -p $ROOT_DIR + +# Init configs +for ((i=0; i<$NUM_NODES; i++)) do + $EVMOSD init node$i --home $ROOT_DIR/node$i --chain-id $CHAIN_ID + + # Change parameter token denominations to aevmos + GENESIS=$ROOT_DIR/node$i/config/genesis.json + TMP_GENESIS=$ROOT_DIR/node$i/config/tmp_genesis.json + cat $GENESIS | jq '.app_state["staking"]["params"]["bond_denom"]="aevmos"' >$TMP_GENESIS && mv $TMP_GENESIS $GENESIS + cat $GENESIS | jq '.app_state["gov"]["params"]["min_deposit"][0]["denom"]="aevmos"' >$TMP_GENESIS && mv $TMP_GENESIS $GENESIS + + # Change app.toml + APP_TOML=$ROOT_DIR/node$i/config/app.toml + sed -i 's/minimum-gas-prices = "0aevmos"/minimum-gas-prices = "1aevmos"/' $APP_TOML + sed -i '/\[json-rpc\]/,/^\[/ s/enable = false/enable = true/' $APP_TOML + + # Change config.toml + CONFIG_TOML=$ROOT_DIR/node$i/config/config.toml + sed -i '/seeds = /c\seeds = ""' $CONFIG_TOML + sed -i 's/addr_book_strict = true/addr_book_strict = false/' $CONFIG_TOML +done + +# Update persistent_peers in config.toml +for ((i=1; i<$NUM_NODES; i++)) do + PERSISTENT_NODES="" + for ((j=0; j<$i; j++)) do + if [[ $j -gt 0 ]]; then PERSISTENT_NODES=$PERSISTENT_NODES,; fi + NODE_ID=`$EVMOSD tendermint show-node-id --home $ROOT_DIR/node$j` + P2P_PORT=$(($P2P_PORT_START+$j)) + PERSISTENT_NODES=$PERSISTENT_NODES$NODE_ID@127.0.0.1:$P2P_PORT + done + sed -i "/persistent_peers = /c\persistent_peers = \"$PERSISTENT_NODES\"" $ROOT_DIR/node$i/config/config.toml +done + +# Create genesis with a single validator +$EVMOSD keys add val0 --keyring-backend test --home $ROOT_DIR/node0 +$EVMOSD add-genesis-account val0 1000000000evmos --keyring-backend test --home $ROOT_DIR/node0 + +# add genesis account for tests, see GENESIS_PRIV_KEY and GENESIS_PRIV_KEY1 in node_config.py +$EVMOSD add-genesis-account evmos1l0j9dqdvd3fatfqywhm4y6avrln4jracaapkme 1000000000evmos --home $ROOT_DIR/node0 +$EVMOSD add-genesis-account evmos1pemg6y3etj9tlhkl0vdwkrw36f74u2nlpxf6wc 1000000000evmos --home $ROOT_DIR/node0 + +mkdir -p $ROOT_DIR/gentxs +$EVMOSD gentx val0 500000000evmos --keyring-backend test --home $ROOT_DIR/node0 --output-document $ROOT_DIR/gentxs/node0.json +$EVMOSD collect-gentxs --home $ROOT_DIR/node0 --gentx-dir $ROOT_DIR/gentxs +$EVMOSD validate-genesis --home $ROOT_DIR/node0 +for ((i=1; i<$NUM_NODES; i++)) do + cp $ROOT_DIR/node0/config/genesis.json $ROOT_DIR/node$i/config/genesis.json +done diff --git a/tests/config/node_config.py b/tests/config/node_config.py index dea655b..1c41724 100644 --- a/tests/config/node_config.py +++ b/tests/config/node_config.py @@ -2,6 +2,7 @@ from web3 import Web3 ZGS_CONFIG = { "log_config_file": "log_config", + "confirmation_block_count": 1, } BSC_CONFIG = dict( @@ -28,6 +29,7 @@ CONFLUX_CONFIG = dict( ) BLOCK_SIZE_LIMIT = 200 * 1024 +# 0xfbe45681Ac6C53D5a40475F7526baC1FE7590fb8 GENESIS_PRIV_KEY = "46b9e861b63d3509c88b7817275a30d22d62c8cd8fa6486ddee35ef0d8e0495f" MINER_ID = "308a6e102a5829ba35e4ba1da0473c3e8bd45f5d3ffb91e31adb43f25463dddb" GENESIS_ACCOUNT = Web3().eth.account.from_key(GENESIS_PRIV_KEY) @@ -37,6 +39,7 @@ TX_PARAMS = { "from": GENESIS_ACCOUNT.address, } +# 0x0e768D12395C8ABFDEdF7b1aEB0Dd1D27d5E2A7F GENESIS_PRIV_KEY1 = "9a6d3ba2b0c7514b16a006ee605055d71b9edfad183aeb2d9790e9d4ccced471" GENESIS_ACCOUNT1 = Web3().eth.account.from_key(GENESIS_PRIV_KEY1) TX_PARAMS1 = { diff --git a/tests/mine_test.py b/tests/mine_test.py index 481727d..97e7f26 100755 --- a/tests/mine_test.py +++ b/tests/mine_test.py @@ -2,7 +2,6 @@ from test_framework.blockchain_node import BlockChainNodeType from test_framework.test_framework import TestFramework from config.node_config import MINER_ID, GENESIS_PRIV_KEY -from test_framework.blockchain_node import BlockChainNodeType from utility.submission import create_submission, submit_data from utility.utils import wait_until diff --git a/tests/mine_with_market_test.py b/tests/mine_with_market_test.py index 2bef35d..52c07a2 100755 --- a/tests/mine_with_market_test.py +++ b/tests/mine_with_market_test.py @@ -2,7 +2,6 @@ from test_framework.blockchain_node import BlockChainNodeType from test_framework.test_framework import TestFramework from config.node_config import MINER_ID, GENESIS_PRIV_KEY -from test_framework.blockchain_node import BlockChainNodeType from utility.submission import create_submission, submit_data from utility.utils import wait_until, assert_equal, assert_greater_than diff --git a/tests/random_test.py b/tests/random_test.py index cc608b7..fc1f9b1 100755 --- a/tests/random_test.py +++ b/tests/random_test.py @@ -2,7 +2,6 @@ import random -from test_framework.blockchain_node import BlockChainNodeType from test_framework.test_framework import TestFramework from utility.submission import create_submission from utility.submission import submit_data @@ -81,4 +80,4 @@ class RandomTest(TestFramework): if __name__ == "__main__": - RandomTest(blockchain_node_type=BlockChainNodeType.Conflux).main() + RandomTest().main() diff --git a/tests/revert_test.py b/tests/revert_test.py index f6fad01..cfc31f5 100755 --- a/tests/revert_test.py +++ b/tests/revert_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from test_framework.test_framework import TestFramework from test_framework.blockchain_node import BlockChainNodeType +from test_framework.test_framework import TestFramework from test_framework.conflux_node import connect_nodes, disconnect_nodes, sync_blocks from config.node_config import CONFLUX_CONFIG, TX_PARAMS1 from utility.submission import create_submission, submit_data diff --git a/tests/test_framework/blockchain_node.py b/tests/test_framework/blockchain_node.py index 8c0e79b..8cd06cd 100644 --- a/tests/test_framework/blockchain_node.py +++ b/tests/test_framework/blockchain_node.py @@ -1,4 +1,3 @@ -import json import os import subprocess import tempfile @@ -14,9 +13,6 @@ from config.node_config import ( GENESIS_PRIV_KEY1, TX_PARAMS, MINER_ID, - NO_MERKLE_PROOF_FLAG, - NO_SEAL_FLAG, - TX_PARAMS1, ) from utility.simple_rpc_proxy import SimpleRpcProxy from utility.utils import ( @@ -30,6 +26,7 @@ from test_framework.contracts import load_contract_metadata class BlockChainNodeType(Enum): Conflux = 0 BSC = 1 + Evmos = 2 @unique @@ -46,7 +43,7 @@ class TestNode: def __init__( self, node_type, index, data_dir, rpc_url, binary, config, log, rpc_timeout=10 ): - assert os.path.exists(binary), ("blockchain binary not found: %s" % binary) + assert os.path.exists(binary), ("Binary not found: %s" % binary) self.node_type = node_type self.index = index self.data_dir = data_dir diff --git a/tests/test_framework/evmos_node.py b/tests/test_framework/evmos_node.py new file mode 100644 index 0000000..517ac88 --- /dev/null +++ b/tests/test_framework/evmos_node.py @@ -0,0 +1,82 @@ +import os +import subprocess +import tempfile + +from test_framework.blockchain_node import BlockChainNodeType, BlockchainNode +from utility.utils import blockchain_rpc_port, arrange_port + +EVMOS_PORT_CATEGORY_WS = 0 +EVMOS_PORT_CATEGORY_P2P = 1 +EVMOS_PORT_CATEGORY_RPC = 2 +EVMOS_PORT_CATEGORY_PPROF = 3 + +def evmos_init_genesis(root_dir: str, num_nodes: int): + assert num_nodes > 0, "Invalid number of blockchain nodes: %s" % num_nodes + + shell_script = os.path.join( + os.path.dirname(os.path.realpath(__file__)), # test_framework folder + "..", "config", "evmos-init-genesis.sh" + ) + + evmosd_dir = os.path.join(root_dir, "evmosd") + os.mkdir(evmosd_dir) + + log_file = tempfile.NamedTemporaryFile(dir=evmosd_dir, delete=False, prefix="init_genesis_", suffix=".log") + p2p_port_start = arrange_port(EVMOS_PORT_CATEGORY_P2P, 0) + + ret = subprocess.run( + args=["bash", shell_script, evmosd_dir, str(num_nodes), str(p2p_port_start)], + stdout=log_file, + stderr=log_file, + ) + + log_file.close() + + assert ret.returncode == 0, "Failed to init evmos genesis, see more details in log file: %s" % log_file.name + +class EvmosNode(BlockchainNode): + def __init__( + self, + index, + root_dir, + binary, + updated_config, + contract_path, + log, + rpc_timeout=10, + ): + assert len(updated_config) == 0, "updated_config not supported for evmos" + + data_dir = os.path.join(root_dir, "evmosd", "node" + str(index)) + rpc_url = "http://127.0.0.1:%s" % blockchain_rpc_port(index) + + super().__init__( + index, + data_dir, + rpc_url, + binary, + {}, + contract_path, + log, + BlockChainNodeType.Evmos, + rpc_timeout, + ) + + self.config_file = None + self.args = [ + binary, "start", + "--home", data_dir, + # overwrite json rpc http port: 8545 + "--json-rpc.address", "127.0.0.1:%s" % blockchain_rpc_port(index), + # overwrite json rpc ws port: 8546 + "--json-rpc.ws-address", "127.0.0.1:%s" % arrange_port(EVMOS_PORT_CATEGORY_WS, index), + # overwrite p2p port: 26656 + "--p2p.laddr", "tcp://127.0.0.1:%s" % arrange_port(EVMOS_PORT_CATEGORY_P2P, index), + # overwrite rpc port: 26657 + "--rpc.laddr", "tcp://127.0.0.1:%s" % arrange_port(EVMOS_PORT_CATEGORY_RPC, index), + # overwrite pprof port: 6060 + "--rpc.pprof_laddr", "127.0.0.1:%s" % arrange_port(EVMOS_PORT_CATEGORY_PPROF, index), + ] + + def setup_config(self): + """ Already batch initialized by shell script in framework """ diff --git a/tests/test_framework/test_framework.py b/tests/test_framework/test_framework.py index e14bc09..4a7160d 100644 --- a/tests/test_framework/test_framework.py +++ b/tests/test_framework/test_framework.py @@ -16,8 +16,8 @@ from test_framework.bsc_node import BSCNode from test_framework.contract_proxy import FlowContractProxy, MineContractProxy, IRewardContractProxy from test_framework.zgs_node import ZgsNode from test_framework.blockchain_node import BlockChainNodeType -from test_framework.conflux_node import ConfluxNode, connect_sample_nodes, sync_blocks - +from test_framework.conflux_node import ConfluxNode, connect_sample_nodes +from test_framework.evmos_node import EvmosNode, evmos_init_genesis from utility.utils import PortMin, is_windows_platform, wait_until __file_path__ = os.path.dirname(os.path.realpath(__file__)) @@ -33,7 +33,7 @@ TEST_EXIT_FAILED = 1 class TestFramework: - def __init__(self, blockchain_node_type=BlockChainNodeType.Conflux): + def __init__(self, blockchain_node_type=BlockChainNodeType.Evmos): if "http_proxy" in os.environ: # Print a warning message in yellow color print("\n\033[93m ⚠️ Warning: You've set the environment variable 'http_proxy', which might lead to testing issues.\033[0m\n") @@ -49,6 +49,7 @@ class TestFramework: self.enable_market = False self.mine_period = 100 + # Set default binary path binary_ext = ".exe" if is_windows_platform() else "" tests_dir = os.path.dirname(__file_path__) root_dir = os.path.dirname(tests_dir) @@ -58,6 +59,9 @@ class TestFramework: self.__default_geth_binary__ = os.path.join( tests_dir, "tmp", "geth" + binary_ext ) + self.__default_evmos_binary__ = os.path.join( + tests_dir, "tmp", "evmosd" + binary_ext + ) self.__default_zgs_node_binary__ = os.path.join( root_dir, "target", "release", "zgs_node" + binary_ext ) @@ -66,6 +70,10 @@ class TestFramework: ) def __setup_blockchain_node(self): + if self.blockchain_node_type == BlockChainNodeType.Evmos: + evmos_init_genesis(self.root_dir, self.num_blockchain_nodes) + self.log.info("Evmos genesis initialized for %s nodes" % self.num_blockchain_nodes) + for i in range(self.num_blockchain_nodes): if i in self.blockchain_node_configs: updated_config = self.blockchain_node_configs[i] @@ -92,6 +100,15 @@ class TestFramework: self.contract_path, self.log, ) + elif self.blockchain_node_type == BlockChainNodeType.Evmos: + node = EvmosNode( + i, + self.root_dir, + self.blockchain_binary, + updated_config, + self.contract_path, + self.log, + ) else: raise NotImplementedError @@ -141,6 +158,14 @@ class TestFramework: # The default is `dev` mode with auto mining, so it's not guaranteed that blocks # can be synced in time for `sync_blocks` to pass. # sync_blocks(self.blockchain_nodes) + elif self.blockchain_node_type == BlockChainNodeType.Evmos: + # wait for the first block + self.log.debug("Wait 3 seconds for evmos node to generate first block") + time.sleep(3) + for node in self.blockchain_nodes: + wait_until(lambda: node.net_peerCount() == self.num_blockchain_nodes - 1) + wait_until(lambda: node.eth_blockNumber() is not None) + wait_until(lambda: int(node.eth_blockNumber(), 16) > 0) contract, tx_hash, mine_contract, reward_contract = self.blockchain_nodes[0].setup_contract(self.enable_market, self.mine_period) self.contract = FlowContractProxy(contract, self.blockchain_nodes) @@ -172,7 +197,7 @@ class TestFramework: ) self.nodes.append(node) node.setup_config() - # wait firt node start for connection + # wait first node start for connection if i > 0: time.sleep(1) node.start() @@ -196,6 +221,13 @@ class TestFramework: type=str, ) + parser.add_argument( + "--evmos-binary", + dest="evmos", + default=self.__default_evmos_binary__, + type=str, + ) + parser.add_argument( "--zerog-storage-binary", dest="zerog_storage", @@ -353,7 +385,7 @@ class TestFramework: self.__setup_zgs_node() def stop_nodes(self): - # stop ionion nodes first + # stop storage nodes first for node in self.nodes: node.stop() @@ -394,8 +426,12 @@ class TestFramework: if self.blockchain_node_type == BlockChainNodeType.Conflux: self.blockchain_binary = os.path.abspath(self.options.conflux) - else: + elif self.blockchain_node_type == BlockChainNodeType.BSC: self.blockchain_binary = os.path.abspath(self.options.bsc) + elif self.blockchain_node_type == BlockChainNodeType.Evmos: + self.blockchain_binary = os.path.abspath(self.options.evmos) + else: + raise NotImplementedError self.zgs_binary = os.path.abspath(self.options.zerog_storage) self.cli_binary = os.path.abspath(self.options.cli) @@ -412,6 +448,7 @@ class TestFramework: try: self.setup_params() self.setup_nodes() + self.log.debug("========== start to run tests ==========") self.run_test() success = TestStatus.PASSED except AssertionError as e: diff --git a/tests/test_framework/zgs_node.py b/tests/test_framework/zgs_node.py index e9e7bd2..1a7f00e 100644 --- a/tests/test_framework/zgs_node.py +++ b/tests/test_framework/zgs_node.py @@ -66,7 +66,7 @@ class ZgsNode(TestNode): os.mkdir(self.data_dir) log_config_path = os.path.join(self.data_dir, self.config["log_config_file"]) with open(log_config_path, "w") as f: - f.write("debug") + f.write("debug,hyper=info,h2=info") initialize_toml_config(self.config_file, self.config) diff --git a/tests/utility/run_all.py b/tests/utility/run_all.py index a46811f..cf5ce75 100644 --- a/tests/utility/run_all.py +++ b/tests/utility/run_all.py @@ -15,6 +15,7 @@ DEFAULT_PORT_MAX = 65535 DEFAULT_PORT_RANGE = 500 CONFLUX_BINARY = "conflux.exe" if is_windows_platform() else "conflux" +EVMOS_BINARY = "evmosd.exe" if is_windows_platform() else "evmosd" def print_testcase_result(color, glyph, script, start_time): print(color[1] + glyph + " Testcase " + script + "\telapsed: " + str(int(time.time() - start_time)) + " seconds" + color[0], flush=True) @@ -59,13 +60,29 @@ def run_single_test(py, script, test_dir, index, port_min, port_max): print_testcase_result(BLUE, TICK, script, start_time) def run_all(test_dir: str, test_subdirs: list[str]=[], slow_tests: set[str]={}, long_manual_tests: set[str]={}): - start_time = time.time() - tmp_dir = os.path.join(test_dir, "tmp") if not os.path.exists(tmp_dir): os.makedirs(tmp_dir, exist_ok=True) - build_conflux_if_absent(tmp_dir) + # Build conflux binary if absent + build_from_github( + dir=tmp_dir, + binary_name=CONFLUX_BINARY, + github_url="https://github.com/Conflux-Chain/conflux-rust.git", + build_cmd="cargo build --release --bin conflux", + compiled_relative_path=["target", "release"], + ) + + # Build evmos binary if absent + build_from_github( + dir=tmp_dir, + binary_name=EVMOS_BINARY, + github_url="https://github.com/0glabs/0g-evmos.git", + build_cmd="make install; cp $(go env GOPATH)/bin/evmosd .", + compiled_relative_path=[], + ) + + start_time = time.time() parser = argparse.ArgumentParser(usage="%(prog)s [options]") parser.add_argument( @@ -139,29 +156,38 @@ def run_all(test_dir: str, test_subdirs: list[str]=[], slow_tests: set[str]={}, print(c) sys.exit(1) - -def build_conflux_if_absent(tmp_dir): - conflux_path = os.path.join(tmp_dir, CONFLUX_BINARY) - if os.path.exists(conflux_path): - return +def build_from_github(dir: str, binary_name: str, github_url: str, build_cmd: str, compiled_relative_path: list[str]) -> bool: + binary_path = os.path.join(dir, binary_name) + if os.path.exists(binary_path): + return False - conflux_tmp_dir = os.path.join(tmp_dir, "conflux_tmp") - if os.path.exists(conflux_tmp_dir): - shutil.rmtree(conflux_tmp_dir) - - clone_command = "git clone https://github.com/Conflux-Chain/conflux-rust.git" - clone_with_path = clone_command + " " + conflux_tmp_dir - os.system(clone_with_path) + start_time = time.time() + + # clone code from github to a temp folder + code_tmp_dir_name = (binary_name[:-4] if is_windows_platform() else binary_name) + "_tmp" + code_tmp_dir = os.path.join(dir, code_tmp_dir_name) + if os.path.exists(code_tmp_dir): + shutil.rmtree(code_tmp_dir) + clone_command = "git clone " + github_url + " " + code_tmp_dir + os.system(clone_command) + # build binary origin_path = os.getcwd() - os.chdir(conflux_tmp_dir) - os.system("cargo build --release --bin conflux") + os.chdir(code_tmp_dir) + os.system(build_cmd) - path = os.path.join(conflux_tmp_dir, "target", "release", CONFLUX_BINARY) - shutil.copyfile(path, conflux_path) + # copy compiled binary to right place + compiled_binary = os.path.join(code_tmp_dir, *compiled_relative_path, binary_name) + shutil.copyfile(compiled_binary, binary_path) if not is_windows_platform(): - st = os.stat(conflux_path) - os.chmod(conflux_path, st.st_mode | stat.S_IEXEC) + st = os.stat(binary_path) + os.chmod(binary_path, st.st_mode | stat.S_IEXEC) os.chdir(origin_path) + + shutil.rmtree(code_tmp_dir, ignore_errors=True) + + print("Completed to build binary " + binary_name + ", Elapsed: " + str(int(time.time() - start_time)) + " seconds", flush=True) + + return True diff --git a/tests/utility/utils.py b/tests/utility/utils.py index 95d3084..3a8ef2f 100644 --- a/tests/utility/utils.py +++ b/tests/utility/utils.py @@ -37,6 +37,8 @@ def blockchain_rpc_port(n): def blockchain_rpc_port_core(n): return PortMin.n + 4 * MAX_NODES + n +def arrange_port(category: int, node_index: int) -> int: + return PortMin.n + (100 + category) * MAX_NODES + node_index def wait_until(predicate, *, attempts=float("inf"), timeout=float("inf"), lock=None): if attempts == float("inf") and timeout == float("inf"):