diff --git a/.gitignore b/.gitignore index ede3817..b8f8688 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # editor related .idea/ +.idea/* # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/setup.py b/setup.py index 21ecec1..1cb87be 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,13 @@ from setuptools import setup setup(name="tinman", - version = __import__('tinman').__version__, - description = "Testnet management scripts.", - url = "https://github.com/steemit/tinman", - author = "Steemit", - packages = ["tinman", "simple_steem_client"], - install_requires = [], - entry_points = {"console_scripts" : [ + version=__import__('tinman').__version__, + description="Testnet management scripts.", + url="https://github.com/steemit/tinman", + author= "Steemit", + packages=["tinman", "simple_steem_client"], + install_requires=[], + entry_points={"console_scripts" : [ "tinman=tinman.main:sys_main", ]} - ) + ) \ No newline at end of file diff --git a/tinman/amountsub.py b/tinman/amountsub.py index b2141aa..664d566 100644 --- a/tinman/amountsub.py +++ b/tinman/amountsub.py @@ -7,6 +7,7 @@ import sys import math + def transform_amounts(object, ratio, floor_satoshi=1): def intersection(a, b): c = [value for value in a if value in b] @@ -35,12 +36,17 @@ def intersection(a, b): field["amount"] = str(new_amount) + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Adjust amount fields by ratio") - parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", help="File to read actions from") - parser.add_argument("-o", "--output-file", default="-", dest="output_file", metavar="FILE", help="File to write actions to") - parser.add_argument("-r", "--ratio", default="1.0", dest="ratio", metavar="FLOAT", help="Adjust amounts in op to ratio") - parser.add_argument("-f", "--floor-satoshi", default="1", dest="floor_satoshi", metavar="INT", help="Minimum amount after ratio is applied") + parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", + help="File to read actions from") + parser.add_argument("-o", "--output-file", default="-", dest="output_file", metavar="FILE", + help="File to write actions to") + parser.add_argument("-r", "--ratio", default="1.0", dest="ratio", metavar="FLOAT", + help="Adjust amounts in op to ratio") + parser.add_argument("-f", "--floor-satoshi", default="1", dest="floor_satoshi", metavar="INT", + help="Minimum amount after ratio is applied") args = parser.parse_args(argv[1:]) if args.output_file == "-": @@ -81,5 +87,6 @@ def main(argv): if args.output_file != "-": output_file.close() + if __name__ == "__main__": main(sys.argv) diff --git a/tinman/durables.py b/tinman/durables.py index d458e7f..cd9c52e 100644 --- a/tinman/durables.py +++ b/tinman/durables.py @@ -7,6 +7,7 @@ from . import prockey from . import util + def build_account_tx(account, keydb, silent=True): name = account["name"] @@ -25,31 +26,36 @@ def build_account_tx(account, keydb, silent=True): "amount" : account["vesting"], }}], "wif_sigs" : [keydb.get_privkey(account["creator"])]} - + + def build_feed_tx(feed, keydb, silent=True): - return {"operations" : [{"type" : "feed_publish_operation", "value" : { - "publisher" : feed["publisher"], - "exchange_rate" : feed["exchange_rate"] + return {"operations" : [{"type" : "feed_publish_operation", "value": { + "publisher": feed["publisher"], + "exchange_rate": feed["exchange_rate"] }}], - "wif_sigs" : [keydb.get_privkey(feed["publisher"])]} - + "wif_sigs": [keydb.get_privkey(feed["publisher"])]} + + def build_actions(conf, silent=True): keydb = prockey.ProceduralKeyDatabase() accounts = conf["accounts"] feeds = conf["feeds"] for account in accounts: - yield ["submit_transaction", {"tx" : build_account_tx(account, keydb, silent)}] + yield ["submit_transaction", {"tx" : build_account_tx(account, keydb, silent)}] for feed in feeds: - yield ["submit_transaction", {"tx" : build_feed_tx(feed, keydb, silent)}] + yield ["submit_transaction", {"tx" : build_feed_tx(feed, keydb, silent)}] return + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Generate durable objects for Steem testnet") - parser.add_argument("-c", "--conffile", default="durables.conf", dest="conffile", metavar="FILE", help="Specify configuration file") - parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", help="Specify output file, - means stdout") + parser.add_argument("-c", "--conffile", default="durables.conf", dest="conffile", metavar="FILE", + help="Specify configuration file") + parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", + help="Specify output file, - means stdout") args = parser.parse_args(argv[1:]) with open(args.conffile, "r") as f: @@ -68,5 +74,6 @@ def main(argv): if args.outfile != "-": outfile.close() + if __name__ == "__main__": main(sys.argv) diff --git a/tinman/gatling.py b/tinman/gatling.py index d36e7be..4c83f4f 100755 --- a/tinman/gatling.py +++ b/tinman/gatling.py @@ -19,12 +19,14 @@ MAX_RETRY = 30 + def str2bool(str_arg): """ Returns boolean True/False if recognized in string argument, None otherwise. """ return True if str_arg.lower() == 'true' else (False if str_arg.lower() == 'false' else None) + def repack_operations(conf, keydb, min_block, max_block, from_blocks_ago, to_blocks_ago): """ Uses configuration file data to acquire operations from source node @@ -62,13 +64,14 @@ def repack_operations(conf, keydb, min_block, max_block, from_blocks_ago, to_blo dgpo = steemd.database_api.get_dynamic_global_properties() new_head_block = dgpo["head_block_number"] while old_head_block == new_head_block: - time.sleep(1) # Theoretically 3 seconds, but most probably we won't have to wait that long. + time.sleep(1) # Theoretically 3 seconds, but most probably we won't have to wait that long. dgpo = steemd.database_api.get_dynamic_global_properties() new_head_block = dgpo["head_block_number"] for op in util.iterate_operations_from(steemd, is_appbase, old_head_block, new_head_block, ported_types): yield op_for_role(op, conf, keydb, ported_operations) old_head_block = new_head_block - return + return # unreachable code + def op_for_role(op, conf, keydb, ported_operations): """ @@ -80,22 +83,25 @@ def op_for_role(op, conf, keydb, ported_operations): for ported_op in ported_operations: if ported_op["type"] == op["type"]: roles = ported_op["roles"] - + + # TODO try: roles except NameError: Do something. + if roles and len(roles) == 1: # It's a trivial role that know about right in config. - return {"operations" : [op], "wif_sigs" : [keydb.get_privkey(tx_signer, roles[0])]} + return {"operations": [op], "wif_sigs": [keydb.get_privkey(tx_signer, roles[0])]} else: # custom_json_operation is usually posting, but sometimes there's an elevated role. if op["type"] in ["custom_json_operation", "custom_binary_operation", "custom_operation"]: if len(op["value"]["required_posting_auths"]) > 0: # The role is "posting" because required_posting_auths has keys. - return {"operations" : [op], "wif_sigs" : [keydb.get_privkey(tx_signer, "posting")]} + return {"operations" : [op], "wif_sigs": [keydb.get_privkey(tx_signer, "posting")]} else: # Assume "active" because there's nothing in required_posting_auths. - return {"operations" : [op], "wif_sigs" : [keydb.get_privkey(tx_signer, "active")]} + return {"operations": [op], "wif_sigs" : [keydb.get_privkey(tx_signer, "active")]} else: # Assume it's "active" as a fallback. - return {"operations" : [op], "wif_sigs" : [keydb.get_privkey(tx_signer, "active")]} + return {"operations" : [op], "wif_sigs": [keydb.get_privkey(tx_signer, "active")]} + def build_actions(conf, min_block, max_block, from_blocks_ago, to_blocks_ago): """ @@ -108,9 +114,10 @@ def build_actions(conf, min_block, max_block, from_blocks_ago, to_blocks_ago): retry_count += 1 try: - for b in util.batch(repack_operations(conf, keydb, min_block, max_block, from_blocks_ago, to_blocks_ago), conf["transactions_per_block"]): + for b in util.batch(repack_operations(conf, keydb, min_block, max_block, from_blocks_ago, to_blocks_ago), + conf["transactions_per_block"]): for tx in b: - yield ["submit_transaction", {"tx" : tx}] + yield ["submit_transaction", {"tx": tx}] retry_count = 0 break except SteemRPCException as e: @@ -131,14 +138,21 @@ def build_actions(conf, min_block, max_block, from_blocks_ago, to_blocks_ago): raise e return + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Port transactions for Steem testnet") - parser.add_argument("-c", "--conffile", default="gatling.conf", dest="conffile", metavar="FILE", help="Specify configuration file") - parser.add_argument("-f", "--from_block", default=-1, dest="min_block_num", metavar="INT", help="Stream from block_num") - parser.add_argument("-t", "--to_block", default=-1, dest="max_block_num", metavar="INT", help="Stream to block_num") - parser.add_argument("-fb", "--from_blocks_ago", default=-1, dest="from_blocks_ago", metavar="INT", help="Stream from relative block_num") - parser.add_argument("-tb", "--to_blocks_ago", default=-1, dest="to_blocks_ago", metavar="INT", help="Stream to relative block_num") - parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", help="Specify output file, - means stdout") + parser.add_argument("-c", "--conffile", default="gatling.conf", dest="conffile", metavar="FILE", + help="Specify configuration file") + parser.add_argument("-f", "--from_block", default=-1, dest="min_block_num", metavar="INT", + help="Stream from block_num") + parser.add_argument("-t", "--to_block", default=-1, dest="max_block_num", metavar="INT", + help="Stream to block_num") + parser.add_argument("-fb", "--from_blocks_ago", default=-1, dest="from_blocks_ago", metavar="INT" + , help="Stream from relative block_num") + parser.add_argument("-tb", "--to_blocks_ago", default=-1, dest="to_blocks_ago", metavar="INT", + help="Stream to relative block_num") + parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", + help="Specify output file, - means stdout") args = parser.parse_args(argv[1:]) with open(args.conffile, "r") as f: @@ -168,5 +182,6 @@ def main(argv): if args.outfile != "-": outfile.close() + if __name__ == "__main__": main(sys.argv) diff --git a/tinman/keysub.py b/tinman/keysub.py index 7167b0e..f6ce55b 100755 --- a/tinman/keysub.py +++ b/tinman/keysub.py @@ -8,6 +8,7 @@ import subprocess import sys + def process_esc(s, esc="", resolver=None): result = [] for e, is_escaped in util.tag_escape_sequences(s, esc): @@ -16,19 +17,21 @@ def process_esc(s, esc="", resolver=None): continue ktype, seed = e.split(":", 1) if ktype == "publickey": - result.append( json.dumps(resolver.get_pubkey(seed))[1:-1] ) + result.append(json.dumps(resolver.get_pubkey(seed))[1:-1]) elif ktype == "privatekey": - result.append( json.dumps(resolver.get_privkey(seed))[1:-1] ) + result.append(json.dumps(resolver.get_privkey(seed))[1:-1]) else: raise RuntimeError("invalid input") return "".join(result) + def compute_keypair_from_seed(seed, secret, get_dev_key_exe="get_dev_key"): result_bytes = subprocess.check_output([get_dev_key_exe, secret, seed]) result_str = result_bytes.decode("utf-8") result_json = json.loads(result_str.strip()) return (result_json[0]["public_key"], result_json[0]["private_key"]) + class ProceduralKeyResolver(object): """ Every synthetic testnet key is generated by concatenating the name, secret and role. @@ -54,11 +57,15 @@ def get_pubkey(self, seed): def get_privkey(self, seed): return self.get(seed)[1] + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Resolve procedural keys") - parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", help="File to read actions from") - parser.add_argument("-o", "--output-file", default="-", dest="output_file", metavar="FILE", help="File to write actions to") - parser.add_argument("--get-dev-key", default="get_dev_key", dest="get_dev_key_exe", metavar="FILE", help="Specify path to get_dev_key tool") + parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", + help="File to read actions from") + parser.add_argument("-o", "--output-file", default="-", dest="output_file", metavar="FILE", + help="File to write actions to") + parser.add_argument("--get-dev-key", default="get_dev_key", dest="get_dev_key_exe", metavar="FILE", + help="Specify path to get_dev_key tool") args = parser.parse_args(argv[1:]) if args.output_file == "-": @@ -93,5 +100,6 @@ def main(argv): if args.output_file != "-": output_file.close() + if __name__ == "__main__": main(sys.argv) diff --git a/tinman/main.py b/tinman/main.py index 67ba1da..7f1bbef 100755 --- a/tinman/main.py +++ b/tinman/main.py @@ -32,13 +32,15 @@ def main(argv): ("sample" , sample ), ("submit" , submit ), ("warden" , warden ), - ("amountsub" , amountsub ), - ("durables" , durables ), + ("amountsub", amountsub), + ("durables", durables), ("prefixsub", prefixsub), - ("help" , Help ), + ("help" , Help), )) + def main(argv): + if len(argv) == 0: argv = list(argv) + ["tinman"] if len(argv) == 1: @@ -53,11 +55,13 @@ def main(argv): return 1 return module.main(argv[1:]) + def sys_main(): result = main(sys.argv) if result is None: result = 0 sys.exit(result) + if __name__ == "__main__": sys_main() diff --git a/tinman/prefixsub.py b/tinman/prefixsub.py index 37e8c29..0510271 100644 --- a/tinman/prefixsub.py +++ b/tinman/prefixsub.py @@ -13,6 +13,7 @@ MAINNET_DISABLED_WITNESS_KEY = "STM1111111111111111111111111111111114T1Anm" TESTNET_DISABLED_WITNESS_KEY = "TST1111111111111111111111111111111114T1Anm" + def transform_prefix(object): if isinstance(object, list): for i, e in enumerate(object): @@ -37,10 +38,13 @@ def transform_prefix(object): else: return object + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Substitute prefix") - parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", help="File to read actions from") - parser.add_argument("-o", "--output-file", default="-", dest="output_file", metavar="FILE", help="File to write actions to") + parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", + help="File to read actions from") + parser.add_argument("-o", "--output-file", default="-", dest="output_file", metavar="FILE", + help="File to write actions to") args = parser.parse_args(argv[1:]) if args.output_file == "-": @@ -75,5 +79,6 @@ def main(argv): if args.output_file != "-": output_file.close() + if __name__ == "__main__": main(sys.argv) diff --git a/tinman/prockey.py b/tinman/prockey.py index e4d9c3b..ea98f57 100644 --- a/tinman/prockey.py +++ b/tinman/prockey.py @@ -8,6 +8,7 @@ def __init__(self, name): self.name = name return + class ProceduralPrivateKey(object): """ A procedural private key to be generated by get_dev_key @@ -17,6 +18,7 @@ def __init__(self, name): self.name = name return + class ProceduralKeyDatabase(object): """ Every synthetic testnet key is generated by concatenating the name, secret and role. @@ -47,6 +49,7 @@ def get_authority(self, name, role="active"): "key_auths":[[self.get_pubkey(name, role),1]], } + class PubkeySerializer(object): """ We want to defer substitution of public keys to a later diff --git a/tinman/sample.py b/tinman/sample.py index 16db557..8d6bd78 100755 --- a/tinman/sample.py +++ b/tinman/sample.py @@ -8,10 +8,13 @@ from . import __version__ + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Generate transactions for Steem testnet") - parser.add_argument("-i", "--infile", default="", dest="infile", metavar="FILE", help="Specify input snapshot, - means stdin") - parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", help="Specify output snapshot, - means stdout") + parser.add_argument("-i", "--infile", default="", dest="infile", metavar="FILE", + help="Specify input snapshot, - means stdin") + parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", + help="Specify output snapshot, - means stdout") args = parser.parse_args(argv[1:]) sample_size = 2000 @@ -81,7 +84,7 @@ def main(argv): print("Balances so far:", len(account_balances)) top_accounts = heapq.nlargest(sample_size, account_balances, - key=lambda a : int(account_balances[a])) + key=lambda a :int(account_balances[a])) print('Found top accounts:', len(top_accounts)) diff --git a/tinman/simple_steem_client b/tinman/simple_steem_client new file mode 160000 index 0000000..1fb4ef5 --- /dev/null +++ b/tinman/simple_steem_client @@ -0,0 +1 @@ +Subproject commit 1fb4ef5a53cff992a2f3de2b88d2129678194971 diff --git a/tinman/snapshot.py b/tinman/snapshot.py index f630fb2..65c4254 100755 --- a/tinman/snapshot.py +++ b/tinman/snapshot.py @@ -23,6 +23,7 @@ "Upstream response error" ] + def list_all_accounts(steemd): """ Generator function providing set of accounts existing in the Main Steem net """ start = "" @@ -64,6 +65,7 @@ def list_all_accounts(steemd): else: raise e + def list_all_witnesses(steemd): """ Generator function providing set of witnesses defined in the Main Steem net """ start = "" @@ -87,9 +89,11 @@ def list_all_witnesses(steemd): if not making_progress: break -# Helper function to reuse code related to collection dump across different usecases + def dump_collection(c, outfile): - """ Allows to dump collection into JSON string. """ + """ Allows to dump collection into JSON string. + # Helper function to reuse code related to collection dump across different usecases + """ outfile.write("[\n") first = True for o in c: @@ -99,26 +103,32 @@ def dump_collection(c, outfile): first = False outfile.write("\n]") + def dump_all_accounts(steemd, outfile): """ Allows to dump into the snapshot all accounts provided by Steem Net""" dump_collection(list_all_accounts(steemd), outfile) + def dump_all_witnesses(steemd, outfile): """ Allows to dump into the snapshot all witnesses provided by Steem Net""" dump_collection(list_all_witnesses(steemd), outfile) + def dump_dgpo(steemd, outfile): """ Allows to dump into the snapshot all Dynamic Global Properties Objects provided by Steem Net """ dgpo = steemd.database_api.get_dynamic_global_properties(x=None) - json.dump( dgpo, outfile, separators=(",", ":"), sort_keys=True ) + json.dump(dgpo, outfile, separators=(",", ":"), sort_keys=True) + def main(argv): """ Tool entry point function """ parser = argparse.ArgumentParser(prog=argv[0], description="Create snapshot files for Steem") - parser.add_argument("-s", "--server", default="http://127.0.0.1:8090", dest="server", metavar="URL", help="Specify mainnet steemd server") - parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", help="Specify output file, - means stdout") + parser.add_argument("-s", "--server", default="http://127.0.0.1:8090", dest="server", metavar="URL", + help="Specify mainnet steemd server") + parser.add_argument("-o", "--outfile", default="-", dest="outfile", metavar="FILE", + help="Specify output file, - means stdout") args = parser.parse_args(argv[1:]) if args.outfile == "-": diff --git a/tinman/submit.py b/tinman/submit.py index 3db4eea..cda309b 100755 --- a/tinman/submit.py +++ b/tinman/submit.py @@ -20,9 +20,11 @@ ACTIONS_MAJOR_VERSION_SUPPORTED = 0 ACTIONS_MINOR_VERSION_SUPPORTED = 2 + class TransactionSigner(object): + def __init__(self, sign_transaction_exe=None, chain_id=None): - if(chain_id is None): + if chain_id is None: self.proc = subprocess.Popen([sign_transaction_exe], stdin=subprocess.PIPE, stdout=subprocess.PIPE) else: self.proc = subprocess.Popen([sign_transaction_exe, "--chain-id="+chain_id], stdin=subprocess.PIPE, stdout=subprocess.PIPE) @@ -37,7 +39,9 @@ def sign_transaction(self, tx, wif): line = self.proc.stdout.readline().decode("utf-8") return json.loads(line) + class CachedDgpo(object): + def __init__(self, timefunc=time.time, refresh_interval=1.0, steemd=None): self.timefunc = timefunc self.refresh_interval = refresh_interval @@ -60,6 +64,7 @@ def get(self): self.last_refresh = now return self.dgpo + def wait_for_real_time(when): while True: rtc_now = datetime.datetime.utcnow() @@ -67,6 +72,7 @@ def wait_for_real_time(when): break time.sleep(0.4) + def generate_blocks(steemd, args, cached_dgpo=None, now=None, produce_realtime=False): if args["count"] <= 0: return @@ -111,6 +117,7 @@ def generate_blocks(steemd, args, cached_dgpo=None, now=None, produce_realtime=F ) return + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Submit transactions to Steem") diff --git a/tinman/txgen.py b/tinman/txgen.py index 860c96d..90d5e8a 100755 --- a/tinman/txgen.py +++ b/tinman/txgen.py @@ -31,28 +31,30 @@ STEEM_MAX_AUTHORITY_MEMBERSHIP = 10 DENOM = 10**12 # we need stupidly high precision because VESTS + def create_system_accounts(conf, keydb, name): desc = conf["accounts"][name] for index in range(desc.get("count", 1)): name = desc["name"].format(index=index) - yield {"operations" : [{"type" : "account_create_operation", "value" : { - "fee" : {"amount" : "0", "precision" : 3, "nai" : "@@000000021"}, - "creator" : desc["creator"], - "new_account_name" : name, - "owner" : keydb.get_authority(name, "owner"), - "active" : keydb.get_authority(name, "active"), - "posting" : keydb.get_authority(name, "posting"), - "memo_key" : keydb.get_pubkey(name, "memo"), - "json_metadata" : "", - }}, {"type" : "transfer_to_vesting_operation", "value" : { - "from" : "initminer", - "to" : name, - "amount" : desc["vesting"], + yield {"operations": [{"type": "account_create_operation", "value": { + "fee": {"amount": "0", "precision": 3, "nai": "@@000000021"}, + "creator": desc["creator"], + "new_account_name": name, + "owner": keydb.get_authority(name, "owner"), + "active": keydb.get_authority(name, "active"), + "posting": keydb.get_authority(name, "posting"), + "memo_key": keydb.get_pubkey(name, "memo"), + "json_metadata": "", + }}, {"type": "transfer_to_vesting_operation", "value": { + "from": "initminer", + "to": name, + "amount": desc["vesting"], }}], - "wif_sigs" : [keydb.get_privkey(desc["creator"])]} + "wif_sigs": [keydb.get_privkey(desc["creator"])]} return + def vote_accounts(conf, keydb, elector, elected): er_desc = conf["accounts"][elector] ed_desc = conf["accounts"][elected] @@ -74,30 +76,31 @@ def vote_accounts(conf, keydb, elector, elected): ops = [] er_name = er_desc["name"].format(index=er_index) for ed_index in votes: - ed_name = ed_desc["name"].format(index=ed_index) - ops.append({"type" : "account_witness_vote_operation", "value" : { - "account" : er_name, - "witness" : ed_name, - "approve" : True, - }}) - yield {"operations" : ops, "wif_sigs" : [keydb.get_privkey(er_name)]} + ed_name = ed_desc["name"].format(index=ed_index) + ops.append( + {"type": "account_witness_vote_operation", + "value": {"account": er_name, "witness" : ed_name, "approve": True, }} + ) + yield {"operations": ops, "wif_sigs": [keydb.get_privkey(er_name)]} return + def update_witnesses(conf, keydb, name): desc = conf["accounts"][name] for index in range(desc["count"]): name = desc["name"].format(index=index) block_signing_key = keydb.get_pubkey(name, 'block') - yield {"operations" : [{"type" : "witness_update_operation", "value" : { - "owner" : name, - "url" : "https://steemit.com/", - "block_signing_key" : block_signing_key, - "props" : {}, - "fee" : amount(0), + yield {"operations": [{"type" : "witness_update_operation", "value" : { + "owner": name, + "url": "https://steemit.com/", + "block_signing_key": block_signing_key, + "props": {}, + "fee": amount(0), }}], - "wif_sigs" : [keydb.get_privkey(name)]} + "wif_sigs": [keydb.get_privkey(name)]} return + def build_setup_transactions(account_stats, conf, keydb, silent=True): yield from create_system_accounts(conf, keydb, "init") yield from create_system_accounts(conf, keydb, "elector") @@ -105,37 +108,40 @@ def build_setup_transactions(account_stats, conf, keydb, silent=True): yield from create_system_accounts(conf, keydb, "porter") yield from port_snapshot(account_stats, conf, keydb, silent) + def build_initminer_tx(conf, keydb): return {"operations" : [ - {"type" : "account_update_operation", - "value" : { - "account" : "initminer", - "owner" : keydb.get_authority("initminer", "owner"), - "active" : keydb.get_authority("initminer", "active"), - "posting" : keydb.get_authority("initminer", "posting"), - "memo_key" : keydb.get_pubkey("initminer", "memo"), - "json_metadata" : "", + {"type": "account_update_operation", + "value": { + "account": "initminer", + "owner": keydb.get_authority("initminer", "owner"), + "active": keydb.get_authority("initminer", "active"), + "posting": keydb.get_authority("initminer", "posting"), + "memo_key": keydb.get_pubkey("initminer", "memo"), + "json_metadata": "", }}, - {"type" : "transfer_to_vesting_operation", - "value" : { - "from" : "initminer", - "to" : "initminer", - "amount" : conf["accounts"]["initminer"]["vesting"], + {"type": "transfer_to_vesting_operation", + "value": { + "from": "initminer", + "to": "initminer", + "amount": conf["accounts"]["initminer"]["vesting"], }}, - {"type" : "account_witness_vote_operation", - "value" : { - "account" : "initminer", - "witness" : "initminer", - "approve" : True, + {"type": "account_witness_vote_operation", + "value": { + "account": "initminer", + "witness": "initminer", + "approve": True, }}, - ], - "wif_sigs" : ["5JNHfZYKGaomSFvd4NUdQ9qMcEAC43kujbfjueTHpVapX1Kzq2n"]} + ], "wif_sigs": ["5JNHfZYKGaomSFvd4NUdQ9qMcEAC43kujbfjueTHpVapX1Kzq2n"]} + def satoshis(s): return int(s["amount"]) + def amount(satoshis, prec=3, symbol="@@000000021"): - return {"amount" : str(satoshis), "precision" : prec, "nai" : symbol} + return {"amount": str(satoshis), "precision": prec, "nai": symbol} + def get_system_account_names(conf): for desc in conf["accounts"].values(): @@ -144,6 +150,7 @@ def get_system_account_names(conf): yield name return + def get_account_stats(conf, silent=True): system_account_names = set(get_system_account_names(conf)) vests = 0 @@ -173,6 +180,7 @@ def get_account_stats(conf, silent=True): "total_steem": total_steem } + def get_proportions(account_stats, conf, silent=True): """ We have a fixed amount of STEEM to give out, specified by total_port_balance @@ -219,6 +227,7 @@ def get_proportions(account_stats, conf, silent=True): "steem_conversion_factor": steem_conversion_factor } + def create_accounts(account_stats, conf, keydb, silent=True): system_account_names = set(get_system_account_names(conf)) proportions = get_proportions(account_stats, conf, silent) @@ -229,7 +238,7 @@ def create_accounts(account_stats, conf, keydb, silent=True): num_accounts = len(account_names) porter = conf["accounts"]["porter"]["name"] porter_wif = keydb.get_privkey("porter") - create_auth = {"account_auths" : [["porter", 1]], "key_auths" : [], "weight_threshold" : 1} + create_auth = {"account_auths": [["porter", 1]], "key_auths": [], "weight_threshold": 1} accounts_created = 0 with open(conf["snapshot_file"], "rb") as f: @@ -242,26 +251,26 @@ def create_accounts(account_stats, conf, keydb, silent=True): name = a["name"] vesting_amount = max(vesting_amount, min_vesting_per_account) - ops = [{"type" : "account_create_operation", "value" : { - "fee" : {"amount" : "0", "precision" : 3, "nai" : "@@000000021"}, - "creator" : porter, - "new_account_name" : name, - "owner" : create_auth, - "active" : create_auth, - "posting" : create_auth, - "memo_key" : "TST"+a["memo_key"][3:], - "json_metadata" : "", - }}, {"type" : "transfer_to_vesting_operation", "value" : { - "from" : porter, - "to" : name, - "amount" : amount(vesting_amount), + ops = [{"type": "account_create_operation", "value": { + "fee": {"amount": "0", "precision": 3, "nai": "@@000000021"}, + "creator": porter, + "new_account_name": name, + "owner": create_auth, + "active": create_auth, + "posting": create_auth, + "memo_key": "TST"+a["memo_key"][3:], + "json_metadata": "", + }}, {"type": "transfer_to_vesting_operation", "value": { + "from": porter, + "to": name, + "amount": amount(vesting_amount), }}] if transfer_amount > 0: - ops.append({"type" : "transfer_operation", "value" : { - "from" : porter, - "to" : name, - "amount" : amount(transfer_amount), - "memo" : "Ported balance", + ops.append({"type": "transfer_operation", "value": { + "from": porter, + "to": name, + "amount": amount(transfer_amount), + "memo": "Ported balance", }}) accounts_created += 1 @@ -270,12 +279,13 @@ def create_accounts(account_stats, conf, keydb, silent=True): print("Accounts created:", accounts_created) print("\t", '%.2f%% complete' % (accounts_created / num_accounts * 100.0)) - yield {"operations" : ops, "wif_sigs" : [porter_wif]} + yield {"operations" : ops, "wif_sigs": [porter_wif]} if not silent: print("Accounts created:", accounts_created) print("\t100.00%% complete") + def update_accounts(account_stats, conf, keydb, silent=True): system_account_names = set(get_system_account_names(conf)) account_names = account_stats["account_names"] @@ -338,23 +348,25 @@ def update_accounts(account_stats, conf, keydb, silent=True): print("Accounts updated:", accounts_updated) print("\t100.00%% complete") + def port_snapshot(account_stats, conf, keydb, silent=True): porter = conf["accounts"]["porter"]["name"] yield {"operations" : [ - {"type" : "transfer_operation", - "value" : {"from" : "initminer", - "to" : porter, - "amount" : conf["total_port_balance"], - "memo" : "Fund porting balances", + {"type": "transfer_operation", + "value": {"from" : "initminer", + "to": porter, + "amount": conf["total_port_balance"], + "memo": "Fund porting balances", }}], - "wif_sigs" : [keydb.get_privkey("initminer")]} + "wif_sigs": [keydb.get_privkey("initminer")]} yield from create_accounts(account_stats, conf, keydb, silent) yield from update_accounts(account_stats, conf, keydb, silent) return + def build_actions(conf, silent=True): keydb = prockey.ProceduralKeyDatabase() account_stats_start = datetime.datetime.utcnow() @@ -451,6 +463,7 @@ def build_actions(conf, silent=True): yield ["wait_blocks", {"count" : NUM_BLOCKS_TO_CLEAR_WITNESS_ROUND}] return + def main(argv): parser = argparse.ArgumentParser(prog=argv[0], description="Generate transactions for Steem testnet") parser.add_argument("-c", "--conffile", default="txgen.conf", dest="conffile", metavar="FILE", help="Specify configuration file") @@ -473,5 +486,6 @@ def main(argv): if args.outfile != "-": outfile.close() + if __name__ == "__main__": main(sys.argv) diff --git a/tinman/util.py b/tinman/util.py index 24850a1..56c66ec 100644 --- a/tinman/util.py +++ b/tinman/util.py @@ -16,6 +16,7 @@ def tag_escape_sequences(s, esc): """ return zip(s.split(esc), itertools.cycle([False, True])) + def batch(it, size=1): """ Change iterable into batches @@ -35,6 +36,7 @@ def batch(it, size=1): yield b return + def find_non_substr(s, alphabet="abcdefghijklmnopqrstuvwxyz", start=""): """ Find a string composed of characters from alphabet that does not occur in s. @@ -87,6 +89,7 @@ def find_non_substr(s, alphabet="abcdefghijklmnopqrstuvwxyz", start=""): return result + def iterate_operations_from(steemd, is_appbase, min_block_number, max_block_number, searched_operation_names): """ Yields operations iterated from provided node's blocks. @@ -125,6 +128,7 @@ def iterate_operations_from(steemd, is_appbase, min_block_number, max_block_numb yield another_operation return + def action_to_str(action): """ This serializes actions, picking a string that does not occur in the JSON diff --git a/tinman/warden.py b/tinman/warden.py index 2b41ee1..e85370e 100644 --- a/tinman/warden.py +++ b/tinman/warden.py @@ -8,12 +8,14 @@ PREFLIGHT_GO = 'go' PREFLIGHT_NOGO = 'nogo' + def main(argv): """ Checks basic node suitability for gatling phase. """ parser = argparse.ArgumentParser(prog=argv[0], description="Generate transactions for Steem testnet") - parser.add_argument("-s", "--server", default="http://127.0.0.1:8090", dest="server", metavar="URL", help="Specify steemd server to watch over") + parser.add_argument("-s", "--server", default="http://127.0.0.1:8090", dest="server", metavar="URL", + help="Specify steemd server to watch over") args = parser.parse_args(argv[1:]) backend = SteemRemoteBackend(nodes=[args.server], appbase=True, max_timeout=0.0, max_retries=0)