diff --git a/.gitignore b/.gitignore index b8392d8643..a266272446 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ release/ /build coverage.txt +trace.out __pycache__ /dist /vendor diff --git a/integration_tests/configs/cosmovisor_recent.jsonnet b/integration_tests/configs/cosmovisor_recent.jsonnet new file mode 100644 index 0000000000..fbe215700d --- /dev/null +++ b/integration_tests/configs/cosmovisor_recent.jsonnet @@ -0,0 +1,34 @@ +local config = import 'default.jsonnet'; + +config { + 'cronos_777-1'+: { + 'app-config'+: { + 'app-db-backend': 'rocksdb', + 'iavl-lazy-loading':: super['iavl-lazy-loading'], + }, + validators: [super.validators[0] { + 'app-config'+: { + store: { + streamers: ['versiondb'], + }, + memiavl:: super.memiavl, + versiondb:: super.versiondb, + }, + }] + super.validators[1:], + genesis+: { + consensus_params: { + block: { + max_bytes: '1048576', + max_gas: '81500000', + }, + }, + app_state+: { + gov+: { + params+: { + expedited_voting_period:: super['expedited_voting_period'], + }, + }, + }, + }, + }, +} diff --git a/integration_tests/configs/upgrade-test-package-recent.nix b/integration_tests/configs/upgrade-test-package-recent.nix new file mode 100644 index 0000000000..9ab30f1534 --- /dev/null +++ b/integration_tests/configs/upgrade-test-package-recent.nix @@ -0,0 +1,26 @@ +let + pkgs = import ../../nix { }; + fetchFlake = + repo: rev: + (pkgs.flake-compat { + src = { + outPath = builtins.fetchTarball "https://github.com/${repo}/archive/${rev}.tar.gz"; + inherit rev; + shortRev = builtins.substring 0 7 rev; + }; + }).defaultNix; + # release/v1.3.x + releasedGenesis = + (fetchFlake "crypto-org-chain/cronos" "e1d819c862b30f0ce978baf2addb12516568639e").default; + current = pkgs.callPackage ../../. { }; +in +pkgs.linkFarm "upgrade-test-package" [ + { + name = "genesis"; + path = releasedGenesis; + } + { + name = "v1.4"; + path = current; + } +] diff --git a/integration_tests/contracts/contracts/CheckpointOracle.sol b/integration_tests/contracts/contracts/CheckpointOracle.sol new file mode 100644 index 0000000000..276360e157 --- /dev/null +++ b/integration_tests/contracts/contracts/CheckpointOracle.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +contract CheckpointOracle { + mapping(address => bool) admins; + address[] adminList; + uint64 sectionIndex; + uint height; + bytes32 hash; + uint sectionSize; + uint processConfirms; + uint threshold; + + function VotingThreshold() public view returns (uint) { + return threshold; + } +} diff --git a/integration_tests/network.py b/integration_tests/network.py index cac096cb9c..775d304c77 100644 --- a/integration_tests/network.py +++ b/integration_tests/network.py @@ -1,7 +1,10 @@ import json import os +import shutil import signal +import stat import subprocess +from contextlib import contextmanager from pathlib import Path import tomlkit @@ -10,7 +13,7 @@ from web3.middleware import geth_poa_middleware from .cosmoscli import CosmosCLI -from .utils import supervisorctl, w3_wait_for_block, wait_for_port +from .utils import post_init, supervisorctl, w3_wait_for_block, wait_for_port class Cronos: @@ -35,6 +38,10 @@ def w3_ws_endpoint(self, i=0): port = ports.evmrpc_ws_port(self.base_port(i)) return f"ws://localhost:{port}" + def pprof_endpoint(self, i=0): + port = ports.pprof_port(self.base_port(i)) + return f"http://localhost:{port}" + @property def w3(self): if self._w3 is None: @@ -193,3 +200,32 @@ def setup_custom_cronos( os.killpg(os.getpgid(proc.pid), signal.SIGTERM) # proc.terminate() proc.wait() + + +def setup_upgrade_cronos(tmp_path_factory, port, nix_name, cfg_name): + path = tmp_path_factory.mktemp("upgrade") + configdir = Path(__file__).parent + cmd = [ + "nix-build", + configdir / f"configs/{nix_name}.nix", + ] + print(*cmd) + subprocess.run(cmd, check=True) + + # copy the content so the new directory is writable. + upgrades = path / "upgrades" + shutil.copytree("./result", upgrades) + mod = stat.S_IRWXU + upgrades.chmod(mod) + for d in upgrades.iterdir(): + d.chmod(mod) + + # init with genesis binary + with contextmanager(setup_custom_cronos)( + path, + port, + configdir / f"configs/{cfg_name}.jsonnet", + post_init=post_init, + chain_binary=str(upgrades / "genesis/bin/cronosd"), + ) as cronos: + yield cronos diff --git a/integration_tests/test_upgrade.py b/integration_tests/test_upgrade.py index 142d5a7429..f37bf5f4ef 100644 --- a/integration_tests/test_upgrade.py +++ b/integration_tests/test_upgrade.py @@ -1,28 +1,21 @@ import json -import shutil -import stat -import subprocess -from contextlib import contextmanager from datetime import datetime, timedelta from pathlib import Path import pytest import requests from pystarport import ports -from pystarport.cluster import SUPERVISOR_CONFIG_FILE -from .network import Cronos, setup_custom_cronos +from .network import Cronos, setup_upgrade_cronos from .utils import ( ADDRS, CONTRACTS, - approve_proposal, assert_gov_params, deploy_contract, - edit_ini_sections, + do_upgrade, get_consensus_params, get_send_enable, send_transaction, - wait_for_block, wait_for_new_blocks, wait_for_port, ) @@ -32,7 +25,10 @@ @pytest.fixture(scope="module") def custom_cronos(tmp_path_factory): - yield from setup_cronos_test(tmp_path_factory) + port = 26200 + nix_name = "upgrade-test-package" + cfg_name = "cosmovisor" + yield from setup_upgrade_cronos(tmp_path_factory, port, nix_name, cfg_name) def get_txs(base_port, end): @@ -44,74 +40,6 @@ def get_txs(base_port, end): return res -def init_cosmovisor(home): - """ - build and setup cosmovisor directory structure in each node's home directory - """ - cosmovisor = home / "cosmovisor" - cosmovisor.mkdir() - (cosmovisor / "upgrades").symlink_to("../../../upgrades") - (cosmovisor / "genesis").symlink_to("./upgrades/genesis") - - -def post_init(path, base_port, config): - """ - prepare cosmovisor for each node - """ - chain_id = "cronos_777-1" - data = path / chain_id - cfg = json.loads((data / "config.json").read_text()) - for i, _ in enumerate(cfg["validators"]): - home = data / f"node{i}" - init_cosmovisor(home) - - edit_ini_sections( - chain_id, - data / SUPERVISOR_CONFIG_FILE, - lambda i, _: { - "command": f"cosmovisor run start --home %(here)s/node{i}", - "environment": ( - "DAEMON_NAME=cronosd," - "DAEMON_SHUTDOWN_GRACE=1m," - "UNSAFE_SKIP_BACKUP=true," - f"DAEMON_HOME=%(here)s/node{i}" - ), - }, - ) - - -def setup_cronos_test(tmp_path_factory): - path = tmp_path_factory.mktemp("upgrade") - port = 26200 - nix_name = "upgrade-test-package" - cfg_name = "cosmovisor" - configdir = Path(__file__).parent - cmd = [ - "nix-build", - configdir / f"configs/{nix_name}.nix", - ] - print(*cmd) - subprocess.run(cmd, check=True) - - # copy the content so the new directory is writable. - upgrades = path / "upgrades" - shutil.copytree("./result", upgrades) - mod = stat.S_IRWXU - upgrades.chmod(mod) - for d in upgrades.iterdir(): - d.chmod(mod) - - # init with genesis binary - with contextmanager(setup_custom_cronos)( - path, - port, - configdir / f"configs/{cfg_name}.jsonnet", - post_init=post_init, - chain_binary=str(upgrades / "genesis/bin/cronosd"), - ) as cronos: - yield cronos - - def assert_evm_params(cli, expected, height): params = cli.query_params("evm", height=height) del params["header_hash_num"] @@ -163,52 +91,11 @@ def exec(c, tmp_path_factory): wait_for_port(ports.evmrpc_port(base_port)) wait_for_new_blocks(cli, 1) - def do_upgrade(plan_name, target, mode=None): - print(f"upgrade {plan_name} height: {target}") - if plan_name == "v1.4.0-rc5-testnet": - rsp = cli.software_upgrade( - "community", - { - "name": plan_name, - "title": "upgrade test", - "note": "ditto", - "upgrade-height": target, - "summary": "summary", - "deposit": "10000basetcro", - }, - ) - assert rsp["code"] == 0, rsp["raw_log"] - approve_proposal(c, rsp["events"]) - else: - rsp = cli.gov_propose_legacy( - "community", - "software-upgrade", - { - "name": plan_name, - "title": "upgrade test", - "description": "ditto", - "upgrade-height": target, - "deposit": "10000basetcro", - }, - mode=mode, - ) - assert rsp["code"] == 0, rsp["raw_log"] - approve_proposal(c, rsp["logs"][0]["events"]) - - # update cli chain binary - c.chain_binary = ( - Path(c.chain_binary).parent.parent.parent / f"{plan_name}/bin/cronosd" - ) - # block should pass the target height - wait_for_block(c.cosmos_cli(), target + 2, timeout=480) - wait_for_port(ports.rpc_port(base_port)) - return c.cosmos_cli() - # test migrate keystore cli.migrate_keystore() height = cli.block_height() target_height0 = height + 15 - cli = do_upgrade("v1.1.0", target_height0, "block") + cli = do_upgrade(c, "v1.1.0", target_height0, "block") check_basic_tx(c) height = cli.block_height() @@ -231,7 +118,7 @@ def do_upgrade(plan_name, target, mode=None): ) print("old values", old_height, old_balance, old_base_fee) - cli = do_upgrade("v1.2", target_height1) + cli = do_upgrade(c, "v1.2", target_height1) check_basic_tx(c) # deploy contract should still work @@ -287,11 +174,11 @@ def do_upgrade(plan_name, target, mode=None): height = cli.block_height() txs = get_txs(base_port, height) - cli = do_upgrade("v1.3", height + 15) + cli = do_upgrade(c, "v1.3", height + 15) assert txs == get_txs(base_port, height) gov_param = cli.query_params("gov") - cli = do_upgrade("v1.4", cli.block_height() + 15) + cli = do_upgrade(c, "v1.4", cli.block_height() + 15) assert_evm_params(cli, e0, target_height0 - 1) assert_evm_params(cli, e1, target_height1 - 1) @@ -302,7 +189,7 @@ def do_upgrade(plan_name, target, mode=None): cli.query_params("icaauth") assert_gov_params(cli, gov_param) - cli = do_upgrade("v1.4.0-rc5-testnet", cli.block_height() + 15) + cli = do_upgrade(c, "v1.4.0-rc5-testnet", cli.block_height() + 15) check_basic_tx(c) diff --git a/integration_tests/test_upgrade_recent.py b/integration_tests/test_upgrade_recent.py new file mode 100644 index 0000000000..e9b7aed515 --- /dev/null +++ b/integration_tests/test_upgrade_recent.py @@ -0,0 +1,69 @@ +import concurrent.futures +from concurrent.futures import ThreadPoolExecutor, as_completed + +import pytest +import requests + +from .network import setup_upgrade_cronos +from .utils import CONTRACTS, deploy_contract, do_upgrade + +pytestmark = pytest.mark.upgrade + + +@pytest.fixture(scope="module") +def custom_cronos(tmp_path_factory): + port = 27100 + nix_name = "upgrade-test-package-recent" + cfg_name = "cosmovisor_recent" + yield from setup_upgrade_cronos(tmp_path_factory, port, nix_name, cfg_name) + + +def call(url, params): + rsp = requests.post(url, json=params) + assert rsp.status_code == 200 + return rsp.json() + + +def call_check(url, address, concurrent): + batch = 10 + param = { + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "data": "0x0be5b6ba", + "to": address, + }, + "latest", + ], + "id": 1, + } + params = [] + for _ in range(batch): + params.append(param) + with ThreadPoolExecutor(concurrent) as executor: + tasks = [executor.submit(call, url, params) for _ in range(0, concurrent)] + results = [future.result() for future in as_completed(tasks)] + assert len(results) == concurrent + + +def call_trace(url): + res = requests.get(f"{url}/debug/pprof/trace?seconds=3") + if res.status_code == 200: + with open("trace.out", "wb") as file: + file.write(res.content) + print("saved trace.out") + else: + print(f"failed to retrieve data: {res.status_code}") + + +def test_cosmovisor_upgrade(custom_cronos): + c = custom_cronos + do_upgrade(c, "v1.4", c.cosmos_cli().block_height() + 15) + res = deploy_contract(c.w3, CONTRACTS["CheckpointOracle"]) + with ThreadPoolExecutor() as exec: + futures = [ + exec.submit(call_trace, c.pprof_endpoint()), + exec.submit(call_check, c.w3_http_endpoint(), res.address, 100), + ] + concurrent.futures.wait(futures) diff --git a/integration_tests/utils.py b/integration_tests/utils.py index 5ea3680dbb..3de987725f 100644 --- a/integration_tests/utils.py +++ b/integration_tests/utils.py @@ -24,7 +24,8 @@ from eth_account import Account from eth_utils import abi, to_checksum_address from hexbytes import HexBytes -from pystarport import ledger +from pystarport import ledger, ports +from pystarport.cluster import SUPERVISOR_CONFIG_FILE from web3._utils.contracts import abi_to_signature, find_matching_event_abi from web3._utils.events import get_event_data from web3._utils.method_formatters import receipt_formatter @@ -63,6 +64,7 @@ "TestICA": "TestICA.sol", "Random": "Random.sol", "TestRelayer": "TestRelayer.sol", + "CheckpointOracle": "CheckpointOracle.sol", } @@ -786,3 +788,82 @@ def assert_gov_params(cli, old_param): expedited_param = get_expedited_params(old_param) for key, value in expedited_param.items(): assert param[key] == value, param + + +def init_cosmovisor(home): + """ + build and setup cosmovisor directory structure in each node's home directory + """ + cosmovisor = home / "cosmovisor" + cosmovisor.mkdir() + (cosmovisor / "upgrades").symlink_to("../../../upgrades") + (cosmovisor / "genesis").symlink_to("./upgrades/genesis") + + +def post_init(path, base_port, config): + """ + prepare cosmovisor for each node + """ + chain_id = "cronos_777-1" + data = path / chain_id + cfg = json.loads((data / "config.json").read_text()) + for i, _ in enumerate(cfg["validators"]): + home = data / f"node{i}" + init_cosmovisor(home) + + edit_ini_sections( + chain_id, + data / SUPERVISOR_CONFIG_FILE, + lambda i, _: { + "command": f"cosmovisor run start --home %(here)s/node{i}", + "environment": ( + "DAEMON_NAME=cronosd," + "DAEMON_SHUTDOWN_GRACE=1m," + "UNSAFE_SKIP_BACKUP=true," + f"DAEMON_HOME=%(here)s/node{i}" + ), + }, + ) + + +def do_upgrade(c, plan_name, target, mode=None): + print(f"upgrade {plan_name} height: {target}") + cli = c.cosmos_cli() + if plan_name == "v1.4.0-rc5-testnet": + rsp = cli.software_upgrade( + "community", + { + "name": plan_name, + "title": "upgrade test", + "note": "ditto", + "upgrade-height": target, + "summary": "summary", + "deposit": "10000basetcro", + }, + ) + assert rsp["code"] == 0, rsp["raw_log"] + approve_proposal(c, rsp["events"]) + else: + rsp = cli.gov_propose_legacy( + "community", + "software-upgrade", + { + "name": plan_name, + "title": "upgrade test", + "description": "ditto", + "upgrade-height": target, + "deposit": "10000basetcro", + }, + mode=mode, + ) + assert rsp["code"] == 0, rsp["raw_log"] + approve_proposal(c, rsp["logs"][0]["events"]) + + # update cli chain binary + c.chain_binary = ( + Path(c.chain_binary).parent.parent.parent / f"{plan_name}/bin/cronosd" + ) + # block should pass the target height + wait_for_block(c.cosmos_cli(), target + 2, timeout=480) + wait_for_port(ports.rpc_port(c.base_port(0))) + return c.cosmos_cli()