Skip to content

Commit

Permalink
Problem: Transaction bot does not work on local node (#308)
Browse files Browse the repository at this point in the history
Solution: (Fix #307) Refactor to be able to work with a local node with more features
  • Loading branch information
calvinlauyh authored Jan 11, 2021
1 parent debaf23 commit b90ed44
Show file tree
Hide file tree
Showing 14 changed files with 1,191 additions and 743 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ frontend/.cache

# Pystarport
/bot.yaml
/bot.*.yaml
/config.yaml
22 changes: 18 additions & 4 deletions bot.yaml.sample
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
jobs:
- label: Test # arbitrary label
node: 0 # index of the node where the `from account` is created
from_account: launch # from account (should be pre-created in the node data folder)
- label: Transfer Test # arbitrary label
type: transfer # support transfer, delegate and withdraw_all_rewards
node: 0 # index of the node in the cluster where the `from account` is created.
# Can be skipped if connecting to a non-cluster node
from_account: launch # from account (should be pre-created in the node data folder)
to_address: cro19zyg34kkrllfqvtumsu3jacg9lgf6lw22xsqye
amount: 1cro
interval: 5 # job interval in second
interval: 5 # job interval in second
- label: Delegation Test
type: delegate
node: 0
from_account: launch
to_validator_address: cro19zyg34kkrllfqvtumsu3jacg9lgf6lw22xsqye
random_amount: [1, 2, "cro"] # random amount
random_interval: [60, 120] # random job interval in second
- label: Withdraw All Rewards Test
type: withdraw_all_rewards
node: 0
from_account: txbot_staking
random_interval: [60, 120]
12 changes: 4 additions & 8 deletions integration_tests/test_gov.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from datetime import timedelta

import pytest
Expand All @@ -16,9 +15,7 @@ def test_param_proposal(cluster, vote_option):
- check the result
- check deposit refunded
"""
max_validators = json.loads(
cluster.raw("q", "staking", "params", output="json", node=cluster.node_rpc(0))
)["max_validators"]
max_validators = cluster.staking_params()["max_validators"]

rsp = cluster.gov_propose(
"community",
Expand Down Expand Up @@ -66,7 +63,8 @@ def test_param_proposal(cluster, vote_option):
rsp = cluster.gov_vote("validator", proposal_id, vote_option, i=1)
assert rsp["code"] == 0, rsp["raw_log"]
assert (
int(cluster.query_tally(proposal_id)[vote_option]) == cluster.staking_pool()
int(cluster.query_tally(proposal_id, i=1)[vote_option])
== cluster.staking_pool()
), "all voted"
else:
assert cluster.query_tally(proposal_id) == {
Expand All @@ -86,9 +84,7 @@ def test_param_proposal(cluster, vote_option):
else:
assert proposal["status"] == "PROPOSAL_STATUS_REJECTED", proposal

new_max_validators = json.loads(
cluster.raw("q", "staking", "params", output="json", node=cluster.node_rpc(0))
)["max_validators"]
new_max_validators = cluster.staking_params()["max_validators"]
if vote_option == "yes":
assert new_max_validators == max_validators + 1
else:
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/test_ibc.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_ibc(cluster):
# all clusters share the same root data directory
data_root = next(iter(cluster.values())).data_root
# call chain-maind directly
raw = next(iter(cluster.values())).raw
raw = next(iter(cluster.values())).cosmos_cli().raw
relayer = ["relayer", "--home", data_root / "relayer"]
# init light clients
for chain_id in cluster:
Expand Down
10 changes: 6 additions & 4 deletions integration_tests/test_reward.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ def test_reward(cluster):
fees = 3_000_000_000
# wait for initial reward processed, so that distribution values can be read
wait_for_block(cluster, 2)
old_commission_amount = cluster.distribution_commision(validator1_operator_address)
old_commission_amount2 = cluster.distribution_commision(validator2_operator_address)
old_commission_amount = cluster.distribution_commission(validator1_operator_address)
old_commission_amount2 = cluster.distribution_commission(
validator2_operator_address,
)
old_community_amount = cluster.distribution_community()
old_reward_amount = cluster.distribution_reward(validator1_address)
old_reward_amount2 = cluster.distribution_reward(validator2_address)
Expand All @@ -31,8 +33,8 @@ def test_reward(cluster):
wait_for_new_blocks(cluster, 2)
signer1_balance = cluster.balance(signer1_address)
assert signer1_balance + fees + amount_to_send == signer1_old_balance
commission_amount = cluster.distribution_commision(validator1_operator_address)
commission_amount2 = cluster.distribution_commision(validator2_operator_address)
commission_amount = cluster.distribution_commission(validator1_operator_address)
commission_amount2 = cluster.distribution_commission(validator2_operator_address)
commission_amount_diff = (commission_amount - old_commission_amount) + (
commission_amount2 - old_commission_amount2
)
Expand Down
6 changes: 3 additions & 3 deletions integration_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import yaml
from dateutil.parser import isoparse
from pystarport import cluster
from pystarport import cluster, ledger
from pystarport.ports import rpc_port


Expand Down Expand Up @@ -105,7 +105,7 @@ def cluster_fixture(
flags=re.M,
)
)
clis[chain_id] = cluster.ClusterCLI(data, chain_id)
clis[chain_id] = cluster.ClusterCLI(data, chain_id=chain_id)

supervisord = cluster.start_cluster(data)
if not quiet:
Expand Down Expand Up @@ -142,7 +142,7 @@ def cluster_fixture(


def get_ledger():
return cluster.Ledger()
return ledger.Ledger()


def parse_events(logs):
Expand Down
18 changes: 18 additions & 0 deletions pystarport/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ $ pystarport cli - --help
...
```

## Transaction Bot

A simple transaction bot that works for cluster created by pystarport as well as a local node

Copy and modify `bot.yaml.sample` to `bot.yaml` with your desired bot configurations.

### If you are running on a pystarport created cluster:
1. Make sure you have provide the `node` for each job in the `bot.yaml`
2. Run the command
```
$ pystarport bot --chain-id=[cluster_chain_id] - start
```

### If you are running on a local node
```
$ pstarport bot --node_rpc=tcp://127.0.0.1:26657 --data=/path/to/your/local/node/home/ - start
```

## docker-compose

When used with `docker-compose` or multiple machines, you need to config hostnames, and you probabely want to use a same
Expand Down
2 changes: 1 addition & 1 deletion pystarport/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
pkgs.poetry2nix.mkPoetryApplication {
projectDir = ./.;
preBuild = ''
sed -i -e 's@CHAIN = "" # edit by nix-build@CHAIN = "${chaind}"@' pystarport/cluster.py
sed -i -e 's@CHAIN = "" # edit by nix-build@CHAIN = "${chaind}"@' pystarport/app.py
'';
}
8 changes: 8 additions & 0 deletions pystarport/pystarport/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os

CHAIN = "" # edit by nix-build
if not CHAIN:
CHAIN = os.environ.get("CHAIN_MAIND", "chain-maind")
IMAGE = "docker.pkg.github.com/crypto-com/chain-main/chain-main-pystarport:latest"

SUPERVISOR_CONFIG_FILE = "tasks.ini"
130 changes: 110 additions & 20 deletions pystarport/pystarport/bot.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,110 @@
import random
import sys
import threading
import time

import yaml

from .cluster import ClusterCLI
from .cosmoscli import CosmosCLI


class TxJobThread(threading.Thread):
def __init__(self, label, job, cluster_cli):
def __init__(self, label, job, cosmos_cli: CosmosCLI):
threading.Thread.__init__(self)
self.label = label
self.job = job
self.cluster_cli = cluster_cli
self.cosmos_cli = cosmos_cli

def transfer_tx_job(self):
from_address = self.cluster_cli.address(
from_address = self.cosmos_cli.address(
self.job["from_account"],
self.job.get("node", 0),
)
to_address = self.job["to_address"]
amount = self.job.get("amount", "1basecro")
interval = self.job.get("interval", 60)
if "random_amount" in self.job:
amount = random_amount(
self.job["random_amount"][0],
self.job["random_amount"][1],
self.job["random_amount"][2],
)
else:
amount = self.job["amount"]

print(
"[%s] Transfer %s from %s to %s"
% (self.label, amount, from_address, to_address)
)
result = self.cosmos_cli.transfer(from_address, to_address, amount)
print(result)

def delegate_tx_job(self):
from_address = self.cosmos_cli.address(self.job["from_account"])
to_address = self.job["to_validator_address"]
if "random_amount" in self.job:
amount = random_amount(
self.job["random_amount"][0],
self.job["random_amount"][1],
self.job["random_amount"][2],
)
else:
amount = self.job["amount"]

print(
"[%s] Delegate %s from %s to %s"
% (self.label, amount, from_address, to_address)
)
result = self.cosmos_cli.delegate_amount(to_address, amount, from_address)
print(result)

def withdraw_all_rewards_job(self):
from_address = self.cosmos_cli.address(self.job["from_account"])
print("[%s] Withdraw all rewards from %s" % (self.label, from_address))
result = self.cosmos_cli.withdraw_all_rewards(from_address)
print(result)

def next_interval(self):
if "random_interval" in self.job:
return random.randint(
self.job["random_interval"][0],
self.job["random_interval"][1],
)
return self.job["interval"]

def run(self):
job_type = self.job["type"]
while 1:
begin = time.time()
print(
"[%s] Transfer %s from %s to %s"
% (self.label, amount, from_address, to_address)
)
result = self.cluster_cli.transfer(from_address, to_address, amount)
print(result)

try:
if job_type == "transfer":
self.transfer_tx_job()
elif job_type == "delegate":
self.delegate_tx_job()
elif job_type == "withdraw_all_rewards":
self.withdraw_all_rewards_job()
else:
print("Unknown job type: %s", job_type)
sys.exit()
except Exception as e:
print("error executing job:", sys.exc_info(), str(e))

interval = self.next_interval()

duration = time.time() - begin
if duration < interval:
sleep = interval - duration
print("Next transfer in %ds ...\n" % sleep)
print("Next %s in %ds ...\n" % (job_type, sleep))
time.sleep(sleep)

def run(self):
# TODO: support more transaction types
self.transfer_tx_job()

def random_amount(min, max, denom):
return "%d%s" % (random.randint(min, max), denom)

class BotCLI:
"transacction bot CLI"

def __init__(self, config_path, cluster_cli):
class BotClusterCLI:
"transaction bot Cluster CLI"

def __init__(self, config_path, cluster_cli: ClusterCLI):
self.config = yaml.safe_load(open(config_path))
self.cluster_cli = cluster_cli

Expand All @@ -53,7 +114,36 @@ def start(self):
"""
threads = []
for i, job in enumerate(self.config["jobs"], start=1):
thread = TxJobThread(job.get("label", i), job, self.cluster_cli)
node_i = job.get("node", 0)
cli = CosmosCLI(
self.cluster_cli.home(node_i),
self.cluster_cli.node_rpc(node_i),
chain_id=self.cluster_cli.chain_id,
cmd=self.cluster_cli.cmd,
)
thread = TxJobThread(job.get("label", i), job, cli)

threads.append(thread)
thread.start()

for thread in threads:
thread.join()


class BotCLI:
"transaction bot CLI"

def __init__(self, config_path, cosmos_cli=None):
self.config = yaml.safe_load(open(config_path))
self.cosmos_cli = cosmos_cli

def start(self):
"""
prepare and start a transaction bot from configuration
"""
threads = []
for i, job in enumerate(self.config["jobs"], start=1):
thread = TxJobThread(job.get("label", i), job, self.cosmos_cli)

threads.append(thread)
thread.start()
Expand Down
Loading

0 comments on commit b90ed44

Please sign in to comment.