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

Utilities for building sifchain-testnet-2 #3431

Open
wants to merge 8 commits into
base: master
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
1 change: 1 addition & 0 deletions test/integration/framework/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/venv/
/build/
/src/siftool/__pycache__/
/test/__pycache__/
/.lock
48 changes: 48 additions & 0 deletions test/integration/framework/src/siftool/chainbuilder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import sys

from siftool import common, command, environments, project, sifchain


def __brutally_terminate_processes(cmd):
project.get_project().pkill()


# This is used for bulding sifchain-testnet-2
def install_testnet(cmd: command.Command, base_dir: str, chain_id: str):
prj = project.get_project(cmd)
mnemonics = prj.read_peruser_config_file("mnemonics")
faucet_mnemonic = mnemonics["sif1zk8wxg65k6702hu9lrxqqnf8vn74ykznf5e7hk"]
node0_admin_mnemonic = mnemonics["sif1kkfa9f2h933uj0u6dexcnucjen6m32vh0jlnq6"]
node1_admin_mnemonic = mnemonics["sif1q0ahne0jkuxf2lj52c35evt6kglsyqlcenwtuk"]
node2_admin_mnemonic = mnemonics["sif1m0dh2e4vyle6svxjukp7e688n340uzqprf4k77"]
node3_admin_mnemonic = mnemonics["sif1q3s9pxvxze0vwkwa34x3jukgumnaefnq24zs36"]
external_host = "147.135.105.196"
extra_denoms = {"testtoken-{}".format(i): 10**30 for i in range(0)} # Caner: we don't want any dummy tokens on testnet
env = environments.SifnodedEnvironment(cmd, chain_id=chain_id, sifnoded_home_root=base_dir)
env.add_validator(moniker="node-0", admin_mnemonic=node0_admin_mnemonic, external_host=external_host, pruning="default")
env.add_validator(moniker="node-1", admin_mnemonic=node1_admin_mnemonic, external_host=external_host, pruning="nothing")
env.add_validator(moniker="node-2", admin_mnemonic=node2_admin_mnemonic, external_host=external_host, pruning="everything")
env.add_validator(moniker="node-3", admin_mnemonic=node3_admin_mnemonic, external_host=external_host, pruning="everything")
env.init(faucet_balance={sifchain.ROWAN: 10**30, sifchain.STAKE: 10**30} | extra_denoms, faucet_mnemonic=faucet_mnemonic)
env.start()
sifnoded = env._client_for()

# Initial configuration of token registry. The method `token_registry_register_batch` already checks the result.
# Compared to https://www.notion.so/sifchain/TestNet-2-7b3df383906c40cd8d4ded7bb5532252?pvs=4#dc261e1451df45e3b06cb0f99c9c692f
# our defaults are display_name = external_symbol = "ROWAN".
sifnoded.token_registry_register_batch(env.clp_admin,
tuple(sifnoded.create_tokenregistry_entry(denom, denom, 18) for denom in [sifchain.ROWAN, sifchain.STAKE]))

sifnoded.wait_for_block(sifnoded.get_current_block() + 10)
__brutally_terminate_processes(cmd)


def main(*argv):
cmd = command.Command()
base_dir = argv[0]
chain_id = argv[1]
install_testnet(cmd, base_dir, chain_id)


if __name__ == "__main__":
main(*sys.argv[1:])
13 changes: 12 additions & 1 deletion test/integration/framework/src/siftool/cosmos.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Balance = Mapping[str, int]
CompatBalance = Union[LegacyBalance, Balance]
Address = str
AccountName = str
Bank = Mapping[Address, Balance]
BechAddress = str
KeyName = str # Name of key in the keyring
Expand Down Expand Up @@ -132,8 +133,18 @@ def transfer(cmd, channel, address, amount, from_addr, chain_id, node, gas_price
args = [akash_binary, "tx", "ibc-transfer", "transfer", "transfer", channel,
address, amount, "--from", from_addr, "--keyring-backend", keyring_backend,
"--chain-id", chain_id, "--node", node, "-y", "--gas-prices", gas_prices,
"--gas", gas, "--packet-timeout-timestam[", str(packet_timeout_timestamp)]
"--gas", gas, "--packet-timeout-timestamp", str(packet_timeout_timestamp)]
res = cmd.execst(args)
return res

# </editor-fold>


# Example: "channel-141", "uosmo" -> "ibc/14F9BC3E44B8A9C1BE1FB08980FAB87034C9905EF17CF2F5008FC085218811CC"
# The channel_id is for the side that is receiving the denom (not the counterparty).
# See https://tutorials.cosmos.network/tutorials/6-ibc-dev/
def derive_ibc_denom_hash(channel: str, denom: str) -> str:
port = "transfer" # Seems to be a constant for IBC token transfers
s = "{}/{}/{}".format(port, channel, denom)
import hashlib
return "ibc/{}".format(hashlib.sha256(s.encode("UTF-8")).digest().hex().upper())
192 changes: 126 additions & 66 deletions test/integration/framework/src/siftool/environments.py

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions test/integration/framework/src/siftool/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def killall(processes):
p.wait()


def get_project(cmd: Command):
return Project(cmd, project_dir())


class Project:
"""Represents a checked out copy of a project in a particular directory."""

Expand Down Expand Up @@ -168,14 +172,14 @@ def write_vagrantenv_sh(self, state_vars, data_dir, ethereum_websocket_address,
self.cmd.write_text_file(vagrantenv_path, joinlines(format_as_shell_env_vars(env)))
self.cmd.write_text_file(project_dir("test/integration/vagrantenv.json"), json.dumps(env))

def get_peruser_config_dir(self):
def get_peruser_config_dir(self) -> str:
return self.cmd.get_user_home(".config", "siftool")

def get_user_env_vars(self):
def get_user_env_vars(self) -> JsonDict:
env_file = os.environ["SIFTOOL_ENV_FILE"]
return json.loads(self.cmd.read_text_file(env_file))

def read_peruser_config_file(self, name):
def read_peruser_config_file(self, name) -> Optional[JsonDict]:
path = os.path.join(self.get_peruser_config_dir(), name + ".json")
if self.cmd.exists(path):
return json.loads(self.cmd.read_text_file(path))
Expand Down
28 changes: 21 additions & 7 deletions test/integration/framework/src/siftool/sifchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import re
import toml
import web3 # TODO Remove dependency
from typing import Mapping, Any, Tuple, AnyStr
from typing import Mapping, Any, Tuple
from siftool import command, cosmos, eth
from siftool.common import *

Expand Down Expand Up @@ -372,13 +372,13 @@ def add_genesis_validators_peggy(self, evm_network_descriptor: int, valoper: cos
self._home_args()
self.sifnoded_exec(args)

def set_genesis_oracle_admin(self, address):
def set_genesis_oracle_admin(self, address: Union[cosmos.AccountName, cosmos.Address]):
self.sifnoded_exec(["set-genesis-oracle-admin", address] + self._home_args() + self._keyring_backend_args())

def set_genesis_token_registry_admin(self, address):
self.sifnoded_exec(["set-genesis-token-registry-admin", address] + self._home_args())

def set_genesis_whitelister_admin(self, address):
def set_genesis_whitelister_admin(self, address: Union[cosmos.AccountName, cosmos.Address]):
self.sifnoded_exec(["set-genesis-whitelister-admin", address] + self._home_args() + self._keyring_backend_args())

def set_gen_denom_whitelist(self, denom_whitelist_file):
Expand Down Expand Up @@ -441,7 +441,7 @@ def query_staking_validators(self) -> JsonObj:
# See scripts/ibc/tokenregistration for more information and examples.
# JSON file can be generated with "sifnoded q tokenregistry generate"
def create_tokenregistry_entry(self, symbol: str, sifchain_symbol: str, decimals: int,
permissions: Iterable[str] = None
permissions: Iterable[str] = None, external_symbol: Optional[str] = None, display_name: Optional[str] = None
) -> TokenRegistryParams:
permissions = permissions if permissions is not None else ["CLP", "IBCEXPORT", "IBCIMPORT"]
upper_symbol = symbol.upper() # Like "USDT"
Expand All @@ -452,11 +452,11 @@ def create_tokenregistry_entry(self, symbol: str, sifchain_symbol: str, decimals
"path": "",
"ibc_channel_id": "",
"ibc_counterparty_channel_id": "",
"display_name": upper_symbol,
"display_name": display_name if display_name is not None else upper_symbol,
"display_symbol": "",
"network": "",
"address": "",
"external_symbol": upper_symbol,
"external_symbol": external_symbol if external_symbol is not None else upper_symbol,
"transfer_limit": "",
"permissions": list(permissions),
"unit_denom": "",
Expand Down Expand Up @@ -1022,6 +1022,20 @@ def version(self) -> str:
def gov_submit_software_upgrade(self, version: str, from_acct: cosmos.Address, deposit: cosmos.Balance,
upgrade_height: int, upgrade_info: str, title: str, description: str, broadcast_mode: Optional[str] = None
):
# Example:
# sifnoded tx gov submit-proposal software-upgrade 1.2.0-beta \
# --title "Sifchain 1.2.0-beta Upgrade" \
# --description "Sifchain 1.2.0-beta Upgrade" \
# --upgrade-height BLOCK_HEIGHT \
# --deposit 50000000000000000000000rowan \
# --from ACCOUNT \
# --keyring-backend test \
# --chain-id=sifchain-1 \
# --node https://sifchain-rpc.polkachu.com:443 \
# --broadcast-mode block \
# --fees 10000000000000000000000rowan \
# --gas 200000 \
# --yes
args = ["tx", "gov", "submit-proposal", "software-upgrade", version, "--from", from_acct, "--deposit",
cosmos.balance_format(deposit), "--upgrade-height", str(upgrade_height), "--upgrade-info", upgrade_info,
"--title", title, "--description", description] + self._home_args() + self._keyring_backend_args() + \
Expand Down Expand Up @@ -1092,7 +1106,7 @@ def _rpc_get(self, host, port, relative_url):
log.debug("Result for {}: {} bytes".format(url, len(http_result_payload)))
return json.loads(http_result_payload.decode("UTF-8"))

def wait_for_last_transaction_to_be_mined(self, count: int = 1, disable_log: bool = True, timeout: int = 90):
def wait_for_last_transaction_to_be_mined(self, count: int = 1, timeout: int = 90):
log.debug("Waiting for last sifnode transaction to be mined...")
start_time = time.time()
initial_block = self.get_current_block()
Expand Down
60 changes: 52 additions & 8 deletions test/integration/framework/test/test_environments.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import contextlib
from siftool.common import *
from siftool import command, environments, project, sifchain, cosmos

Expand All @@ -13,10 +12,31 @@ def test_transfer(env):
sifnoded.send_and_check(env.faucet, sifnoded.create_addr(), {sifchain.ROWAN: 10 ** sifchain.ROWAN_DECIMALS})


def assert_validators_working(env, expected_monikers):
assert set(get_validators(env)) == expected_monikers
for i in range(len(env.node_info)):
test_transfer(env)
def assert_validators_working(env):
expected_monikers = [node_info["moniker"] for node_info in env.node_info if node_info["is_validator"]]
assert set(get_validators(env)) == set(expected_monikers)
number_of_nodes = len(env.node_info)
for i in range(number_of_nodes):
# Send transactions using "keyring" profile that has all the private keys, but is directed at a particular node.
# Cross check balaces on all nodes.
sifnoded = env._client_for(i)
from_address = env.faucet
to_address = sifnoded.create_addr() # This pollutes "client" keyring with temporary addresses, but we don't care
from_balance_before = sifnoded.get_balance(from_address)
to_balance_before = sifnoded.get_balance(to_address)
for j in range(number_of_nodes):
sifnoded_crosscheck = env._client_for(j)
assert cosmos.balance_equal(sifnoded_crosscheck.get_balance(from_address), from_balance_before)
assert cosmos.balance_equal(sifnoded_crosscheck.get_balance(to_address), to_balance_before)
sifnoded.send_and_check(from_address, to_address, {sifchain.ROWAN: 10 ** sifchain.ROWAN_DECIMALS})
at_block = sifnoded.get_current_block()
from_balance_after = sifnoded.get_balance(from_address)
to_balance_after = sifnoded.get_balance(to_address)
for j in range(number_of_nodes):
sifnoded_crosscheck = env._client_for(j)
sifnoded_crosscheck.wait_for_block(at_block) # Make sure the change has propagated
assert cosmos.balance_equal(sifnoded_crosscheck.get_balance(from_address), from_balance_after)
assert cosmos.balance_equal(sifnoded_crosscheck.get_balance(to_address), to_balance_after)


class TestSifnodedEnvironment:
Expand All @@ -32,20 +52,44 @@ def teardown_method(self):
prj = project.Project(self.cmd, project_dir())
prj.pkill()

def test_environment_setup_basic(self):
def test_environment_setup_basic_1_validator(self):
env = environments.SifnodedEnvironment(self.cmd, sifnoded_home_root=self.sifnoded_home_root)
env.add_validator()
env.start()
assert_validators_working(env, set("sifnoded-{}".format(i) for i in range(1)))
assert_validators_working(env)

def test_environment_setup_basic_4_validators(self):
env = environments.SifnodedEnvironment(self.cmd, chain_id="cownet-2", sifnoded_home_root=self.sifnoded_home_root)
assert len(env.node_info) == 0
env.add_validator(moniker="ant")
env.add_validator(moniker="bee")
env.add_validator(moniker="cat")
env.add_validator(moniker="dog")
assert len(env.node_info) == 4
env.start()
assert_validators_working(env)

def test_environment_setup_mix_of_nodes_and_validators(self):
env = environments.SifnodedEnvironment(self.cmd, chain_id="cownet-2", sifnoded_home_root=self.sifnoded_home_root)
assert len(env.node_info) == 0
env.add_validator(moniker="ant", is_validator=True)
env.add_validator(moniker="bee", is_validator=False)
env.add_validator(moniker="cat", is_validator=True)
assert len(env.node_info) == 3
env.start()
assert_validators_working(env)

def test_add_validator_before_and_after_start(self):
env = environments.SifnodedEnvironment(self.cmd, sifnoded_home_root=self.sifnoded_home_root)
assert len(env.node_info) == 0
env.add_validator()
env.add_validator()
assert len(env.node_info) == 2
env.init()
env.start()
env.add_validator()
assert_validators_working(env, set("sifnoded-{}".format(i) for i in range(3)))
assert len(env.node_info) == 3
assert_validators_working(env)

def test_environment_fails_to_start_if_commission_rate_is_over_max(self):
env = environments.SifnodedEnvironment(self.cmd, sifnoded_home_root=self.sifnoded_home_root)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from siftool.common import *
from siftool import command, environments, project


# The goal of this test is to setup an local environment for testing IBC transfers.
# We are trying to spin up single-node sifnoded validator + single-node axelard validator, and then setup and run
# and Osmosis relayer ("rly") to relay IBC tokens between the chains.
# In addition to sifnoded, axelard and rly must be in PATH.
# TODO Not working yet
class TestSifnodedEnvironmentWithIBCTransfers:
def setup_method(self):
self.cmd = command.Command()
self.sifnoded_home_root = self.cmd.tmpdir("siftool.tmp")
self.cmd.rmdir(self.sifnoded_home_root)
self.cmd.mkdir(self.sifnoded_home_root)
prj = project.Project(self.cmd, project_dir())
prj.pkill()

def teardown_method(self):
prj = project.Project(self.cmd, project_dir())
prj.pkill()

def test_ibc_setup_and_transfers(self):
env = environments.SifnodedEnvironment(self.cmd, sifnoded_home_root=self.sifnoded_home_root)
env.add_validator()
env.start()

axelard = Axelard(home=os.path.join(env.sifnoded_home_root + "axelard"))


class Axelard:
def __init__(self, /, chain_id: Optional[str] = None, binary: Optional[str] = None, home: Optional[str] = None):
self.chain_id = chain_id or "axelar-localnet"
self.binary = binary or "axelard"
self.home = home
self.ports = None

def start(self):
pass
# For generic Cosmos-based chain:
# - <binary> keys add <admin-name>
# - <binary> init <moniker>
# - <binary> add-genesis-account ...
# - <binary> add-genesis-validator ... <--- no such command in axelard
# - <binary> gentx <admin-name> <stake-amount> --home <home> --keyring-backend test --chain-id <chain-id>
# - <binary> collect-gentxs --home <home>
# - <binary> validate-genesis
2 changes: 1 addition & 1 deletion test/integration/framework/test/test_sifnoded.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_batching_and_paged_reads(self):
clp_admin = validator0_admin

sifnoded = sifchain.Sifnoded(self.cmd, home=tmpdir, chain_id=env.chain_id,
node=sifchain.format_node_url(env.node_info[0]["host"], env.node_info[0]["ports"]["rpc"]))
node=sifchain.format_node_url(env.node_info[0]["external_host"], env.node_info[0]["external_port"]))
test_addr_actual_balance = sifnoded.get_balance(test_addr)
assert test_addr_actual_balance == test_addr_balance

Expand Down