Skip to content

Commit

Permalink
Problem (fix #170): missing validator joining integration test (#178)
Browse files Browse the repository at this point in the history
Solution:
- add utility to create node at runtime
- add create-validator, edit-validator integration tests
  • Loading branch information
yihuang authored Oct 15, 2020
1 parent fac7171 commit 91fb3e7
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 22 deletions.
9 changes: 9 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
[settings]
# compatible with black
# https://black.readthedocs.io/en/stable/the_black_code_style.html
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88

known_thirdparty=pystarport
42 changes: 41 additions & 1 deletion integration_tests/test_staking.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from .utils import wait_for_new_blocks
from pystarport.ports import rpc_port

from .utils import wait_for_new_blocks, wait_for_port


def test_staking_delegate(cluster):
Expand Down Expand Up @@ -62,3 +64,41 @@ def test_staking_redelegate(cluster):
delegation_info = cluster.get_delegated_amount(signer1_address)
output = delegation_info["delegation_responses"][0]["balance"]["amount"]
assert int(old_output) + 2 == int(output)


def test_join_validator(cluster):
i = cluster.create_node()
addr = cluster.address("validator", i)
# transfer 1cro from ecosystem account
assert cluster.transfer(cluster.address("ecosystem"), addr, "1cro")["code"] == 0
assert cluster.balance(addr) == 10 ** 8

# start the node
cluster.supervisor.startProcess(f"node{i}")
wait_for_port(rpc_port(cluster.base_port, i))

count1 = len(cluster.validators())

# create validator tx
assert cluster.create_validator("1cro", i)["code"] == 0
wait_for_new_blocks(cluster, 2)

count2 = len(cluster.validators())
assert count2 == count1 + 1, "new validator should joined successfully"

val_addr = cluster.address("validator", i, bech="val")
val = cluster.validator(val_addr)
assert not val["jailed"]
assert val["status"] == "BOND_STATUS_BONDED"
assert val["tokens"] == str(10 ** 8)
assert val["description"]["moniker"] == f"node{i}"
assert val["commission"]["commission_rates"] == {
"rate": "0.100000000000000000",
"max_rate": "0.200000000000000000",
"max_change_rate": "0.010000000000000000",
}
assert (
cluster.edit_validator(i, commission_rate="0.2")["code"] == 13
), "commission cannot be changed more than once in 24h"
assert cluster.edit_validator(i, moniker="awesome node")["code"] == 0
assert cluster.validator(val_addr)["description"]["moniker"] == "awesome node"
2 changes: 1 addition & 1 deletion integration_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def cluster_fixture(config_path, base_port, tmp_path_factory, quiet=False):
cluster.init_cluster(data, config, base_port)

# replace the first node with the instrumented binary
ini = data / "tasks.ini"
ini = data / cluster.SUPERVISOR_CONFIG_FILE
ini.write_text(
re.sub(
r"^command = (.*/)?chain-maind",
Expand Down
10 changes: 8 additions & 2 deletions pystarport/pystarport/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
import yaml

from .bot import BotCLI
from .cluster import CHAIN, ClusterCLI, init_cluster, start_cluster
from .cluster import (
CHAIN,
SUPERVISOR_CONFIG_FILE,
ClusterCLI,
init_cluster,
start_cluster,
)
from .utils import interact


Expand Down Expand Up @@ -83,7 +89,7 @@ def serve(
def supervisorctl(self, *args, data: str = "./data"):
from supervisor.supervisorctl import main

main(("-c", Path(data) / "tasks.ini", *args))
main(("-c", Path(data) / SUPERVISOR_CONFIG_FILE, *args))

def cli(self, *args, data: str = "./data", cmd: str = CHAIN):
return ClusterCLI(Path(data), cmd)
Expand Down
183 changes: 165 additions & 18 deletions pystarport/pystarport/cluster.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import configparser
import datetime
import json
import os
import re
import subprocess
import sys
from pathlib import Path
Expand All @@ -17,22 +19,33 @@

CHAIN = "chain-maind" # edit by nix-build

COMMON_PROG_OPTIONS = {
# redirect to supervisord's stdout, easier to collect all logs
"stdout_logfile": "/dev/fd/1",
"stdout_logfile_maxbytes": "0",
"autostart": "true",
"autorestart": "true",
"redirect_stderr": "true",
"startsecs": "3",
}
SUPERVISOR_CONFIG_FILE = "tasks.ini"


def home_dir(data_dir, i):
return data_dir / f"node{i}"


class ChainCommand:
def __init__(self, cmd=None):
self._cmd = cmd or CHAIN
self.cmd = cmd or CHAIN

def __call__(self, cmd, *args, **kwargs):
"execute chain-maind"
args = [cmd] + list(args)
for k, v in kwargs.items():
args.append("--" + k.strip("_").replace("_", "-"))
args.append(v)
return interact(" ".join((self._cmd, *map(str, args))))
args.append('"%s"' % v)
return interact(" ".join((self.cmd, *map(str, args))))


class ClusterCLI:
Expand All @@ -48,6 +61,8 @@ def __init__(self, data_dir, cmd=None):

@property
def supervisor(self):
"http://supervisord.org/api.html"
# copy from:
# https://github.com/Supervisor/supervisor/blob/76df237032f7d9fbe80a0adce3829c8b916d5b58/supervisor/options.py#L1718
if self._supervisorctl is None:
self._supervisorctl = xmlrpclib.ServerProxy(
Expand All @@ -62,6 +77,57 @@ def supervisor(self):
)
return self._supervisorctl.supervisor

def nodes_len(self):
"find how many 'node{i}' sub-directories"
return len(
[p for p in self.data_dir.iterdir() if re.match(r"^node\d+$", p.name)]
)

def create_node(self):
"""create new node in the data directory,
process information is written into supervisor config
start it manually with supervisor commands
:return: new node index
"""
# init home directory
i = self.nodes_len()
self.init(i)
home = self.home(i)
(home / "config/genesis.json").unlink()
(home / "config/genesis.json").symlink_to("../../genesis.json")
# get peers from node0's config
node0 = tomlkit.parse((self.data_dir / "node0/config/config.toml").read_text())
edit_tm_cfg(
home / "config/config.toml",
self.base_port,
i,
node0["p2p"]["persistent_peers"],
)
edit_app_cfg(home / "config/app.toml", self.base_port, i)

# create validator account
self.create_account("validator", i)

# add process config into supervisor
path = self.data_dir / SUPERVISOR_CONFIG_FILE
ini = configparser.ConfigParser()
ini.read_file(path.open())
section = f"program:node{i}"
ini.add_section(section)
ini[section].update(
dict(
COMMON_PROG_OPTIONS,
command=f"{self.raw.cmd} start --home %(here)s/node{i}",
autostart="false",
)
)
ini.write(path.open("w"))
(added, _, _) = self.supervisor.reloadConfig()[0]
assert f"node{i}" in added
self.supervisor.addProcessGroup(f"node{i}")
return i

def home(self, i):
"home directory of i-th node"
return home_dir(self.data_dir, i)
Expand Down Expand Up @@ -308,9 +374,96 @@ def unjail(self, addr, i=0):
)
)

def create_validator(
self,
amount,
i,
moniker=None,
commission_max_change_rate="0.01",
commission_rate="0.1",
commission_max_rate="0.2",
min_self_delegation="1",
identity="",
website="",
security_contact="",
details="",
):
"""MsgCreateValidator
create the node with create_node before call this"""
pubkey = (
self.raw("tendermint", "show-validator", home=self.home(i)).strip().decode()
)
return json.loads(
self.raw(
"tx",
"staking",
"create-validator",
"-y",
from_=self.address("validator", i),
amount=amount,
pubkey=pubkey,
min_self_delegation=min_self_delegation,
# commision
commission_rate=commission_rate,
commission_max_rate=commission_max_rate,
commission_max_change_rate=commission_max_change_rate,
# description
moniker=moniker or f"node{i}",
identity=identity,
website=website,
security_contact=security_contact,
details=details,
# basic
home=self.home(i),
node=self.node_rpc(0),
keyring_backend="test",
chain_id=self.chain_id,
)
)

def edit_validator(
self,
i,
commission_rate=None,
moniker=None,
identity=None,
website=None,
security_contact=None,
details=None,
):
"""MsgEditValidator"""
options = dict(
commission_rate=commission_rate,
# description
moniker=moniker,
identity=identity,
website=website,
security_contact=security_contact,
details=details,
)
return json.loads(
self.raw(
"tx",
"staking",
"edit-validator",
"-y",
from_=self.address("validator", i),
home=self.home(i),
node=self.node_rpc(0),
keyring_backend="test",
chain_id=self.chain_id,
**{k: v for k, v in options.items() if v is not None},
)
)


def start_cluster(data_dir, quiet=False):
cmd = [sys.executable, "-msupervisor.supervisord", "-c", data_dir / "tasks.ini"]
cmd = [
sys.executable,
"-msupervisor.supervisord",
"-c",
data_dir / SUPERVISOR_CONFIG_FILE,
]
env = dict(os.environ, PYTHONPATH=":".join(sys.path))
if quiet:
return subprocess.Popen(
Expand All @@ -337,11 +490,11 @@ def init_cluster(data_dir, config, base_port, cmd=None):
os.mkdir(data_dir / "gentx")
for i in range(len(config["validators"])):
try:
os.remove(data_dir / f"node{i}/config/genesis.json")
(data_dir / f"node{i}/config/genesis.json").unlink()
except OSError:
pass
os.symlink("../../genesis.json", data_dir / f"node{i}/config/genesis.json")
os.symlink("../../gentx", data_dir / f"node{i}/config/gentx")
(data_dir / f"node{i}/config/genesis.json").symlink_to("../../genesis.json")
(data_dir / f"node{i}/config/gentx").symlink_to("../../gentx")

(data_dir / "base_port").write_text(str(base_port))

Expand Down Expand Up @@ -420,17 +573,11 @@ def init_cluster(data_dir, config, base_port, cmd=None):
"supervisorctl": {"serverurl": "unix://%(here)s/supervisor.sock"},
}
for i, node in enumerate(config["validators"]):
supervisord_ini[f"program:node{i}"] = {
"command": f"{cmd} start --home %(here)s/node{i}",
# redirect to supervisord's stdout, easier to collect all logs
"stdout_logfile": "/dev/fd/1",
"stdout_logfile_maxbytes": "0",
"autostart": "true",
"autorestart": "true",
"redirect_stderr": "true",
"startsecs": "3",
}
write_ini(open(data_dir / "tasks.ini", "w"), supervisord_ini)
supervisord_ini[f"program:node{i}"] = dict(
COMMON_PROG_OPTIONS,
command=f"{cmd} start --home %(here)s/node{i}",
)
write_ini(open(data_dir / SUPERVISOR_CONFIG_FILE, "w"), supervisord_ini)


def edit_tm_cfg(path, base_port, i, peers):
Expand Down

0 comments on commit 91fb3e7

Please sign in to comment.