Skip to content

Commit

Permalink
Major overhaul that should've been multiple PRs (#1)
Browse files Browse the repository at this point in the history
Signed-off-by: Dylan Schultz <hello@schultzie.dev>
  • Loading branch information
dylanschultzie committed Feb 14, 2023
1 parent 3a33e85 commit 81a9d81
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 38 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ This will output two different kinds of files
* `/tmp/dist_<denom>_<batch #>.json` which is the unsigned JSON representation of a batch transaction
* `~/dist_<denom>_<batch #>_signed.json` which represents the signed, but not yet broadcast batch transaaction

In addition to the original Lavender.Five nodes version of this script, there are two new command
line options, `--dry_run` and `-f`/`--refund_file`. Details below:

```bash
$ python3 src/slash_refund.py --help
usage: slash_refund.py [-h] --denom DENOM --daemon DAEMON -c CHAIN_ID -e ENDPOINT -vc VALCONS_ADDRESS -v VALOPER_ADDRESS -s SEND_ADDRESS [-m MEMO] -k KEYNAME [--dry_run [DRY_RUN]] [-f REFUND_FILE]
Expand Down
3 changes: 3 additions & 0 deletions refunds.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
address,amount
decentr1qd5cvs4tel4a5zzgq2qgsmvyfzp7ytpxpu80vh,136813
Total Refund Amount,136813
Empty file added src/__init__.py
Empty file.
141 changes: 106 additions & 35 deletions src/slash_refund.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from subprocess import run
from time import sleep
from utils.csv_utils import writeRefundsCsv

BIN_DIR = "" # if this isn't empty, make sure it ends with a slash

Expand All @@ -14,6 +15,7 @@
logger.addHandler(stream_handler)
logger.setLevel(logging.INFO)


def getResponse(end_point, query_field=None, query_msg=None):
response = None

Expand All @@ -29,11 +31,15 @@ def getResponse(end_point, query_field=None, query_msg=None):
return json.loads(response.text)
else:
if response is not None:
logger.error('\n\t'.join((
"Response Error",
str(response.status_code),
str(response.text),
)))
logger.error(
"\n\t".join(
(
"Response Error",
str(response.status_code),
str(response.text),
)
)
)
else:
logger.error("Response is None")

Expand All @@ -60,16 +66,16 @@ def getDelegationAmounts(
while more_pages:
endpoint_choice = (page % len(endpoints)) - 1
command = f"{BIN_DIR}{daemon} q staking delegations-to {valoper_address} --height {block_height} --page {page} --output json --limit {page_limit} --node {endpoints[endpoint_choice]} --chain-id {chain_id}"
logger.debug(f'Delegation amount command: {command}')
logger.info(f'Page: {page}')
logger.debug(f"Delegation amount command: {command}")
logger.info(f"Page: {page}")
result = run(
command,
shell=True,
capture_output=True,
text=True,
)
if result.returncode == 1:
logger.info(f'Failed endpoint: {endpoints[endpoint_choice]}')
logger.info(f"Failed endpoint: {endpoints[endpoint_choice]}")
continue
response = json.loads(result.stdout)

Expand All @@ -96,11 +102,11 @@ def calculateRefundAmounts(
pre_slash_delegations = getDelegationAmounts(
daemon, endpoint, chain_id, pre_slack_block, valoper_address
)
logger.debug(f'Pre slash amounts: {pre_slash_delegations}')
logger.debug(f"Pre slash amounts: {pre_slash_delegations}")
post_slash_delegations = getDelegationAmounts(
daemon, endpoint, chain_id, slash_block, valoper_address
)
logger.debug(f'Post slash amounts: {post_slash_delegations}')
logger.debug(f"Post slash amounts: {post_slash_delegations}")

if len(pre_slash_delegations) != len(post_slash_delegations):
raise ("Something went awry on delegation calcs")
Expand All @@ -111,7 +117,7 @@ def calculateRefundAmounts(
if refund_amount > 10000:
refund_amounts[delegation_address] = refund_amount

logger.info(f'Refund amounts: {len(refund_amounts)}')
logger.info(f"Refund amounts: {len(refund_amounts)}")
return refund_amounts


Expand Down Expand Up @@ -176,27 +182,47 @@ def buildRefundScript(


def issue_refunds(
batch_count: int, daemon: str, chain_id: str, keyname: str, node: str
batch_count: int,
daemon: str,
chain_id: str,
keyname: str,
node: str,
broadcast: bool = True,
):
i = 0
while i < batch_count:
command = f"{BIN_DIR}{daemon} tx sign /tmp/dist_{i}.json --from {keyname} -ojson --output-document ~/dist_signed.json --node {node} --chain-id {chain_id} --keyring-backend test",
logger.debug(f'command being run: {command}')
result = run(
f"{BIN_DIR}{daemon} tx sign /tmp/dist_{i}.json --from {keyname} -ojson --output-document ~/dist_signed.json --node {node} --chain-id {chain_id} --keyring-backend test",
shell=True,
capture_output=True,
text=True,
sign_cmd = (
f"{BIN_DIR}{daemon} tx sign /tmp/dist_{i}.json --from {keyname} -ojson "
f"--output-document ~/dist_{i}_signed.json --node {node} --chain-id {chain_id} "
f"--keyring-backend test"
)
sleep(1)
broadcast_cmd = (
f"{BIN_DIR}{daemon} tx broadcast ~/dist_{i}_signed.json --node {node} "
f"--chain-id {chain_id}"
)

# sign refund
result = run(
f"{BIN_DIR}{daemon} tx broadcast ~/dist_signed.json --node {node} --chain-id {chain_id}",
sign_cmd,
shell=True,
capture_output=True,
text=True,
)
i += 1
sleep(15)
sleep(1)

if broadcast:
# broadcast refund
result = run(
broadcast_cmd,
shell=True,
capture_output=True,
text=True,
)
logger.info(f"Broadcasted refund: {result}")

# if this is not the last batch, sleep
if i < batch_count:
sleep(16)


def parseArgs():
Expand Down Expand Up @@ -266,9 +292,55 @@ def parseArgs():
required=True,
help="Wallet to issue refunds from",
)
parser.add_argument(
"-f",
"--refund_file",
dest="refund_file",
required=False,
default=None,
type=open,
help=(
"CSV file that encodes the delegator addresses and refund amounts. Note: delegator "
"address is expected to be in the first column and the refund amount in [DENOM] is "
"expected to be in the fourth column."
),
)
parser.add_argument(
"--dry_run",
dest="dry_run",
action="store_const",
required=False,
default=False,
const=True,
help="Indicates whether this should actually broadcast transactions or not",
)
parser.add_argument(
"--no_broadcast",
dest="no_broadcast",
action="store_const",
required=False,
default=False,
const=True,
help=(
"Similar to dry run, but in this case the tx JSON is output and signed, but not "
"broadcast. This is useful for testing."
),
)
return parser.parse_args()


def get_daemon_path(daemon: str) -> str:
result = run(
f"which {daemon}",
shell=True,
capture_output=True,
text=True,
)
binary_path = result.stdout.strip().removesuffix(daemon)
logger.info(f"Binary path: {binary_path}")
return binary_path


def main():
global BIN_DIR
args = parseArgs()
Expand All @@ -281,28 +353,27 @@ def main():
send_address = args.send_address
memo = args.memo
keyname = args.keyname
refund_file = args.refund_file
dry_run = args.dry_run
should_broadcast = not args.no_broadcast
logger.debug(f"DEBUG: args: {args}")

BIN_DIR = get_daemon_path(daemon)

slash_block = getSlashBlock(endpoint, valcons_address)
logger.info(f'Slash block: {slash_block}')
logger.info(f"Slash block: {slash_block}")
refund_amounts = calculateRefundAmounts(
daemon, endpoint, chain_id, slash_block, valoper_address
)
batch_count = buildRefundScript(refund_amounts, send_address, denom, memo)
issue_refunds(batch_count, daemon, chain_id, keyname, endpoint)

writeRefundsCsv(refund_amounts)

def get_daemon_path(daemon: str) -> str:
result = run(
f"which {daemon}",
shell=True,
capture_output=True,
text=True,
batch_count = buildRefundScript(refund_amounts, send_address, denom, memo)
if not dry_run:
issue_refunds(
batch_count, daemon, chain_id, keyname, endpoint, should_broadcast
)
binary_path = result.stdout.strip().removesuffix(daemon)
logger.info(f'Binary path: {binary_path}')
return binary_path


if __name__ == "__main__":
main()
Empty file added src/utils/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions src/utils/csv_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import csv
from decimal import Decimal

DENOM_EXPONENTS = {
"ATOM": 0,
"uatom": 6,
"OSMO": 0,
"uosmo": 6,
}


def writeRefundsCsv(refund_amounts: dict):
header = ["address", "amount"]
refund_sum = 0
with open("refunds.csv", "w") as f:
# create the csv writer
writer = csv.writer(f)
writer.writerow(header)

for k in refund_amounts.items():
_, refund_amount = k
writer.writerow(k)
refund_sum += refund_amount

writer.writerow(["Total Refund Amount", refund_sum])


def getRefundAmountsFromCSV(file_obj, denom):
refund_amounts = {}
refund_reader = csv.reader(file_obj, delimiter=",", quotechar="|")
denom_multiplier = 10 ** DENOM_EXPONENTS.get(denom, 1)
for row in refund_reader:
if "address" in row[0]:
continue
delegation_addr = row[0]
refund_amt = Decimal(row[3]) * denom_multiplier
refund_amounts[delegation_addr] = refund_amt

return refund_amounts

0 comments on commit 81a9d81

Please sign in to comment.