示例#1
0
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")
    args = parser.parse_args(argv[1:])

    if args.outfile == "-":
        outfile = sys.stdout
    else:
        outfile = open(args.outfile, "w")

    backend = SteemRemoteBackend(nodes=[args.server], appbase=True)
    steemd = SteemInterface(backend)

    outfile.write("{\n")
    outfile.write('"metadata":{"snapshot:semver":"%s","snapshot:origin_api":"%s"}' % (__version__, args.server))
    outfile.write(',\n"dynamic_global_properties":')
    dump_dgpo(steemd, outfile)
    outfile.write(',\n"accounts":')
    dump_all_accounts(steemd, outfile)
    outfile.write(',\n"witnesses":')
    dump_all_witnesses(steemd, outfile)
    outfile.write("\n}\n")
    outfile.flush()
    if args.outfile != "-":
        outfile.close()
    return
示例#2
0
文件: gatling.py 项目: steemit/tinman
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
    blocks/transactions and repack them in new transactions one to one.
    """

    source_node = conf["transaction_source"]["node"]
    is_appbase = str2bool(conf["transaction_source"]["appbase"])
    backend = SteemRemoteBackend(nodes=[source_node], appbase=is_appbase)
    steemd = SteemInterface(backend)
    dgpo = steemd.database_api.get_dynamic_global_properties()

    if min_block == 0:
        min_block = dgpo["head_block_number"]

    if from_blocks_ago != -1:
        min_block = dgpo["head_block_number"] - from_blocks_ago

    if to_blocks_ago != -1:
        max_block = dgpo["head_block_number"] - to_blocks_ago

    ported_operations = conf["ported_operations"]
    ported_types = set([op["type"] for op in ported_operations])
    """ Positive value of max_block means get from [min_block_number,max_block_number) range and stop """
    if max_block > 0:
        for op in util.iterate_operations_from(steemd, is_appbase, min_block,
                                               max_block, ported_types):
            yield op_for_role(op, conf, keydb, ported_operations)
        return
    """
    Otherwise get blocks from min_block_number to current head and again
    until you have to wait for another block to be produced (chase-then-listen mode)
    """
    old_head_block = min_block
    while True:
        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.
            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
示例#3
0
def repack_operations(conf, keydb, min_block, max_block):
    """
    Uses configuration file data to acquire operations from source node
    blocks/transactions and repack them in new transactions one to one.
    """
    source_node = conf["transaction_source"]["node"]
    is_appbase = str2bool(conf["transaction_source"]["appbase"])
    backend = SteemRemoteBackend(nodes=[source_node], appbase=is_appbase)
    steemd = SteemInterface(backend)
    min_block = int(conf["min_block_number"]) if min_block == 0 else min_block
    max_block = int(conf["max_block_number"]) if max_block == 0 else max_block
    ported_operations = set(conf["ported_operations"])
    tx_signer = conf["transaction_signer"]
    """ Positive value of max_block means get from [min_block_number,max_block_number) range and stop """
    if max_block > 0:
        for op in util.iterate_operations_from(steemd, is_appbase, min_block,
                                               max_block, ported_operations):
            yield {
                "operations": [op],
                "wif_sigs": [keydb.get_privkey(tx_signer)]
            }
        return
    """
    Otherwise get blocks from min_block_number to current head and again
    until you have to wait for another block to be produced (chase-then-listen mode)
    """
    old_head_block = min_block
    while True:
        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.
            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_operations):
            yield {
                "operations": [op],
                "wif_sigs": [keydb.get_privkey(tx_signer)]
            }
        old_head_block = new_head_block
    return
示例#4
0
 def test_iterate_operations_from(self):
     backend = SteemRemoteBackend(nodes=["https://api.steemit.com"], appbase=True)
     steemd = SteemInterface(backend)
     result = util.iterate_operations_from(steemd, True, 1102, 1103, set())
     expected_op = {
         'type': 'pow_operation',
         'value': {
             'worker_account': 'steemit11',
             'block_id': '0000044df0f062c0504a8e37288a371ada63a1c7',
             'nonce': 33097,
             'work': {
                 'worker': 'STM65wH1LZ7BfSHcK69SShnqCAH5xdoSZpGkUjmzHJ5GCuxEK9V5G',
                 'input': '45a3824498b87e41129f6fef17be276af6ff87d1e859128f28aaa9c08208871d',
                 'signature': '1f93a52c4f794803b2563845b05b485e3e5f4c075ddac8ea8cffb988a1ffcdd1055590a3d5206a3be83cab1ea548fc52889d43bdbd7b74d62f87fb8e2166145a5d',
                 'work': '00003e554a58830e7e01669796f40d1ce85c7eb979e376cb49e83319c2688c7e',
             }, 'props': {
                 'account_creation_fee': {"amount" : "100000", "precision" : 3, "nai" : "@@000000021"},
                 'maximum_block_size': 131072,
                 'sbd_interest_rate': 1000
             }
         }
     }
     
     # Scan all of the results and match against the expected op.  This will
     # fail if we get anything other than this exact op.
     for op in result:
         self.assertEqual(op['type'], expected_op['type'])
         self.assertEqual(op['value']['worker_account'], expected_op['value']['worker_account'])
         self.assertEqual(op['value']['block_id'], expected_op['value']['block_id'])
         self.assertEqual(op['value']['nonce'], expected_op['value']['nonce'])
         self.assertEqual(op['value']['work']['worker'], expected_op['value']['work']['worker'])
         self.assertEqual(op['value']['work']['input'], expected_op['value']['work']['input'])
         self.assertEqual(op['value']['work']['signature'], expected_op['value']['work']['signature'])
         self.assertEqual(op['value']['work']['work'], expected_op['value']['work']['work'])
         self.assertEqual(op['value']['props']['account_creation_fee']['amount'], expected_op['value']['props']['account_creation_fee']['amount'])
         self.assertEqual(op['value']['props']['account_creation_fee']['precision'], expected_op['value']['props']['account_creation_fee']['precision'])
         self.assertEqual(op['value']['props']['account_creation_fee']['nai'], expected_op['value']['props']['account_creation_fee']['nai'])
         self.assertEqual(op['value']['props']['maximum_block_size'], expected_op['value']['props']['maximum_block_size'])
         self.assertEqual(op['value']['props']['sbd_interest_rate'], expected_op['value']['props']['sbd_interest_rate'])
示例#5
0
def main(argv):

    parser = argparse.ArgumentParser(
        prog=argv[0], description="Submit transactions to Steem")
    parser.add_argument(
        "-t",
        "--testserver",
        default="http://127.0.0.1:8190",
        dest="testserver",
        metavar="URL",
        help="Specify testnet steemd server with debug enabled")
    parser.add_argument("--signer",
                        default="sign_transaction",
                        dest="sign_transaction_exe",
                        metavar="FILE",
                        help="Specify path to sign_transaction tool")
    parser.add_argument("-i",
                        "--input-file",
                        default="-",
                        dest="input_file",
                        metavar="FILE",
                        help="File to read transactions from")
    parser.add_argument(
        "-f",
        "--fail-file",
        default="-",
        dest="fail_file",
        metavar="FILE",
        help="File to write failures, - for stdout, die to quit on failure")
    parser.add_argument("-n",
                        "--chain-name",
                        default="",
                        dest="chain_name",
                        metavar="CN",
                        help="Specify chain name")
    parser.add_argument("-c",
                        "--chain-id",
                        default="",
                        dest="chain_id",
                        metavar="CID",
                        help="Specify chain ID")
    parser.add_argument("--timeout",
                        default=5.0,
                        type=float,
                        dest="timeout",
                        metavar="SECONDS",
                        help="API timeout")
    parser.add_argument("--realtime",
                        dest="realtime",
                        action="store_true",
                        help="Wait when asked to produce blocks in the future")
    args = parser.parse_args(argv[1:])

    die_on_fail = False
    if args.fail_file == "-":
        fail_file = sys.stdout
    elif args.fail_file == "die":
        fail_file = sys.stdout
        die_on_fail = True
    else:
        fail_file = open(args.fail_file, "w")

    if args.input_file == "-":
        input_file = sys.stdin
    else:
        input_file = open(args.input_file, "r")

    timeout = args.timeout

    backend = SteemRemoteBackend(nodes=[args.testserver],
                                 appbase=True,
                                 min_timeout=timeout,
                                 max_timeout=timeout)
    steemd = SteemInterface(backend)
    sign_transaction_exe = args.sign_transaction_exe
    produce_realtime = args.realtime

    cached_dgpo = CachedDgpo(steemd=steemd)

    if args.chain_name != "":
        chain_id = hashlib.sha256(str.encode(
            args.chain_name.strip())).digest().hex()
    else:
        chain_id = None

    if args.chain_id != "":
        chain_id = args.chain_id.strip()

    signer = TransactionSigner(sign_transaction_exe=sign_transaction_exe,
                               chain_id=chain_id)

    for line in input_file:
        line = line.strip()
        cmd, args = json.loads(line)

        try:
            if cmd == "wait_blocks":
                generate_blocks(steemd,
                                args,
                                cached_dgpo=cached_dgpo,
                                produce_realtime=produce_realtime)
                cached_dgpo.reset()
            elif cmd == "submit_transaction":
                tx = args["tx"]
                dgpo = cached_dgpo.get()
                tx["ref_block_num"] = dgpo["head_block_number"] & 0xFFFF
                tx["ref_block_prefix"] = struct.unpack_from(
                    "<I", unhexlify(dgpo["head_block_id"]), 4)[0]
                head_block_time = datetime.datetime.strptime(
                    dgpo["time"], "%Y-%m-%dT%H:%M:%S")
                expiration = head_block_time + datetime.timedelta(minutes=1)
                expiration_str = expiration.strftime("%Y-%m-%dT%H:%M:%S")
                tx["expiration"] = expiration_str

                wif_sigs = tx["wif_sigs"]
                del tx["wif_sigs"]

                sigs = []
                for wif in wif_sigs:
                    if not isinstance(wif_sigs, list):
                        raise RuntimeError("wif_sigs is not list")
                    result = signer.sign_transaction(tx, wif)
                    if "error" in result:
                        print("could not sign transaction", tx,
                              "due to error:", result["error"])
                    else:
                        sigs.append(result["result"]["sig"])
                tx["signatures"] = sigs
                print("bcast:", json.dumps(tx, separators=(",", ":")))

                steemd.network_broadcast_api.broadcast_transaction(trx=tx)
        except Exception as e:
            fail_file.write(json.dumps([cmd, args, str(e)]) + "\n")
            fail_file.flush()
            if die_on_fail:
                raise
示例#6
0
from simple_steem_client.client import SteemRemoteBackend, SteemInterface

import json

backend = SteemRemoteBackend(nodes=["http://127.0.0.1:9990/"], appbase=True)
steem = SteemInterface(backend)

dgpo = steem.database_api.get_dynamic_global_properties()

print("dgpo:", json.dumps(dgpo, separators=(",", ":")))
示例#7
0
 def test_list_all_accounts(self):
     backend = SteemRemoteBackend(nodes=["http://test.com"], appbase=True)
     steemd = SteemInterface(backend)
     self.assertIsNotNone(snapshot.list_all_accounts(steemd))
示例#8
0
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")
    args = parser.parse_args(argv[1:])
    
    backend = SteemRemoteBackend(nodes=[args.server], appbase=True, max_timeout=0.0, max_retries=0)
    steemd = SteemInterface(backend)
    passfail = []
    
    config = steemd.database_api.get_config(x=None)
    dgpo = steemd.database_api.get_dynamic_global_properties(x=None)
    head_block_time = datetime.datetime.strptime(dgpo["time"], "%Y-%m-%dT%H:%M:%S")
    rtc_now = datetime.datetime.utcnow()
    diff = rtc_now - head_block_time
    diff = diff.total_seconds()
    block_interval = config["STEEM_BLOCK_INTERVAL"]
    
    if config["IS_TEST_NET"]:
        print("[√] testnet: true")
        passfail.append(PREFLIGHT_GO)
    else:
        print("[X] testnet: false")
        passfail.append(PREFLIGHT_NOGO)
    
    if diff < 0:
        print("[X] head block time: %s seconds into the future" % abs(diff))
        passfail.append(PREFLIGHT_NOGO)
    elif diff - block_interval > 0:
        print("[X] head block time: %s seconds behind" % diff)
        passfail.append(PREFLIGHT_NOGO)
    else:
        print("[√] head block time: within %s seconds" % block_interval)
        passfail.append(PREFLIGHT_GO)
    
    witness_schedule = steemd.database_api.get_witness_schedule(x=None)
    witnesses = witness_schedule["current_shuffled_witnesses"]
    initminer = config["STEEM_INIT_MINER_NAME"]
    
    if initminer not in witnesses:
        print("[√] witnesses: %s not present" % initminer)
        passfail.append(PREFLIGHT_GO)
    else:
        print("[X] witnesses: %s present" % initminer)
        passfail.append(PREFLIGHT_NOGO)
    
    scheduled_witnesses = witness_schedule["num_scheduled_witnesses"]
    
    if scheduled_witnesses == config["STEEM_MAX_WITNESSES"]:
        print("[√] scheduled witnesses: %s" % config["STEEM_MAX_WITNESSES"])
        passfail.append(PREFLIGHT_GO)
    else:
        print("[X] scheduled witnesses: %s" % scheduled_witnesses)
        passfail.append(PREFLIGHT_NOGO)
    
    majority_version = witness_schedule["majority_version"]
    
    if majority_version == '0.0.0':
        print("[X] majority version: 0.0.0")
        passfail.append(PREFLIGHT_NOGO)
    else:
        print("[√] majority version: %s" % majority_version)
        passfail.append(PREFLIGHT_GO)
    
    exit(PREFLIGHT_NOGO in passfail) # Also tell the caller everything is ok.
示例#9
0
文件: server.py 项目: steemit/tinman
def main(argv):
    parser = argparse.ArgumentParser(prog=argv[0], description="Web Server")
    parser.add_argument("-c",
                        "--conffile",
                        default="server.conf",
                        dest="conffile",
                        metavar="FILE",
                        help="Specify configuration file")
    parser.add_argument("--signer",
                        default="sign_transaction",
                        dest="sign_transaction_exe",
                        metavar="FILE",
                        help="Specify path to sign_transaction tool")
    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("-n",
                        "--chain-name",
                        default="",
                        dest="chain_name",
                        metavar="CN",
                        help="Specify chain name")
    parser.add_argument("-cid",
                        "--chain-id",
                        default="",
                        dest="chain_id",
                        metavar="CID",
                        help="Specify chain ID")
    parser.add_argument("--timeout",
                        default=5.0,
                        type=float,
                        dest="timeout",
                        metavar="SECONDS",
                        help="API timeout")
    args = parser.parse_args(argv[1:])

    with open(args.conffile, "r") as f:
        conf = json.load(f)

    timeout = args.timeout

    node = conf["transaction_target"]["node"]
    shared_secret = conf["shared_secret"]
    account_creator = conf["account_creator"]
    result_bytes = subprocess.check_output(
        [args.get_dev_key_exe, shared_secret, "active-" + account_creator])
    result_str = result_bytes.decode("utf-8")
    result_json = json.loads(result_str.strip())
    account_creator_wif = result_json[0]["private_key"]
    backend = SteemRemoteBackend(nodes=[node],
                                 appbase=True,
                                 min_timeout=timeout,
                                 max_timeout=timeout)
    steemd = SteemInterface(backend)
    sign_transaction_exe = args.sign_transaction_exe

    if args.chain_name != "":
        chain_id = hashlib.sha256(str.encode(
            args.chain_name.strip())).digest().hex()
    else:
        chain_id = None

    if args.chain_id != "":
        chain_id = args.chain_id.strip()

    signer = submit.TransactionSigner(
        sign_transaction_exe=sign_transaction_exe, chain_id=chain_id)

    template_dir = '/tmp/tinman-templates'
    static_dir = '/tmp/tinman-static'

    app = Flask(__name__,
                template_folder=template_dir,
                static_folder=static_dir,
                static_url_path='/static')
    app.debug = True

    # Temporary development secret key (for web forms).
    app.config['SECRET_KEY'] = '5333d026583fdd09f413d472b29ed39e'

    @app.route("/account_create", methods=['GET', 'POST'])
    def account_create():
        form = ReusableForm(request.form)

        print(form.errors)
        if request.method == 'POST':
            new_account_name = request.form['new_account_name']

            if form.validate():
                key_types = ["owner", "active", "posting", "memo"]
                keys = {}

                for key_type in key_types:
                    result_bytes = subprocess.check_output([
                        args.get_dev_key_exe, shared_secret,
                        key_type + "-" + new_account_name
                    ])
                    result_str = result_bytes.decode("utf-8")
                    result_json = json.loads(result_str.strip())

                    keys[key_type] = result_json[0]

                tx = {
                    "operations": [{
                        "type": "account_create_operation",
                        "value": {
                            "creator": account_creator,
                            "new_account_name": new_account_name,
                            "fee": {
                                "amount": "0",
                                "nai": "@@000000021",
                                "precision": 3
                            },
                            "owner": {
                                "account_auths": [["tnman", 1]],
                                "key_auths": [[keys["owner"]["public_key"],
                                               1]],
                                "weight_threshold": 1
                            },
                            "active": {
                                "account_auths": [["tnman", 1]],
                                "key_auths":
                                [[keys["active"]["public_key"], 1]],
                                "weight_threshold": 1
                            },
                            "posting": {
                                "account_auths": [["tnman", 1]],
                                "key_auths":
                                [[keys["posting"]["public_key"], 1]],
                                "weight_threshold": 1
                            },
                            "memo_key": keys["memo"]["public_key"],
                            "json_metadata": ""
                        }
                    }, {
                        "type": "transfer_to_vesting_operation",
                        "value": {
                            "amount": {
                                "amount": "1000000",
                                "nai": "@@000000021",
                                "precision": 3
                            },
                            "from": account_creator,
                            "to": new_account_name
                        }
                    }],
                    "signatures": []
                }

                cached_dgpo = submit.CachedDgpo(steemd=steemd)
                dgpo = cached_dgpo.get()
                tx["ref_block_num"] = dgpo["head_block_number"] & 0xFFFF
                tx["ref_block_prefix"] = struct.unpack_from(
                    "<I", unhexlify(dgpo["head_block_id"]), 4)[0]
                head_block_time = datetime.datetime.strptime(
                    dgpo["time"], "%Y-%m-%dT%H:%M:%S")
                expiration = head_block_time + datetime.timedelta(minutes=1)
                expiration_str = expiration.strftime("%Y-%m-%dT%H:%M:%S")
                tx["expiration"] = expiration_str

                result = signer.sign_transaction(tx, account_creator_wif)
                if "error" in result:
                    print("could not sign transaction", tx, "due to error:",
                          result["error"])
                else:
                    tx["signatures"].append(result["result"]["sig"])

                print("bcast:", json.dumps(tx, separators=(",", ":")))

                try:
                    steemd.network_broadcast_api.broadcast_transaction(trx=tx)
                    flash("Account Created: " + new_account_name)

                    for key in keys:
                        flash(key + ": " + keys[key]["private_key"])
                except SteemRPCException as e:
                    cause = e.args[0].get("error")
                    if cause:
                        message = cause.get("message")
                        data = cause.get("data")
                    else:
                        message = str(e)
                    print(str(e))
                    flash("Unable to create account: " + message)
            else:
                flash('All the form fields are required.')

        return render_template('account_create.html', form=form)

    app.run()
示例#10
0
def main(argv):

    parser = argparse.ArgumentParser(prog=argv[0], description="Submit transactions to Steem")
    parser.add_argument("-t", "--testserver", default="http://127.0.0.1:8190", dest="testserver", metavar="URL", help="Specify testnet steemd server with debug enabled")
    parser.add_argument("--signer", default="sign_transaction", dest="sign_transaction_exe", metavar="FILE", help="Specify path to sign_transaction tool")
    parser.add_argument("-i", "--input-file", default="-", dest="input_file", metavar="FILE", help="File to read transactions from")
    parser.add_argument("-f", "--fail-file", default="-", dest="fail_file", metavar="FILE", help="File to write failures, - for stdout, die to quit on failure")
    parser.add_argument("-n", "--chain-name", default="", dest="chain_name", metavar="CN", help="Specify chain name")
    parser.add_argument("-c", "--chain-id", default="", dest="chain_id", metavar="CID", help="Specify chain ID")
    parser.add_argument("-tpb", "--transactions-per-block", default="40", dest="transactions_per_block", metavar="INT", help="Transactions per block (default: 40)")
    parser.add_argument("--timeout", default=5.0, type=float, dest="timeout", metavar="SECONDS", help="API timeout")
    parser.add_argument("--realtime", dest="realtime", action="store_true", help="Wait when asked to produce blocks in the future")
    args = parser.parse_args(argv[1:])

    die_on_fail = False
    if args.fail_file == "-":
        fail_file = sys.stdout
    elif args.fail_file == "die":
        fail_file = sys.stdout
        die_on_fail = True
    else:
        fail_file = open(args.fail_file, "w")

    if args.input_file == "-":
        input_file = sys.stdin
    else:
        input_file = open(args.input_file, "r")

    timeout = args.timeout

    backend = SteemRemoteBackend(nodes=[args.testserver], appbase=True, min_timeout=timeout, max_timeout=timeout)
    steemd = SteemInterface(backend)
    sign_transaction_exe = args.sign_transaction_exe
    produce_realtime = args.realtime

    cached_dgpo = CachedDgpo(steemd=steemd)

    if args.chain_name != "":
        chain_id = hashlib.sha256(str.encode(args.chain_name.strip())).digest().hex()
    else:
        chain_id = None

    if args.chain_id != "":
        chain_id = args.chain_id.strip()

    transactions_per_block = int(args.transactions_per_block)
    transactions_count = 0
    signer = TransactionSigner(sign_transaction_exe=sign_transaction_exe, chain_id=chain_id)
    metadata = None

    for line in input_file:
        line = line.strip()
        cmd, args = json.loads(line)

        try:
            if cmd == "metadata":
                metadata = args
                
                if args.get("post_backfill"):
                    dgpo = cached_dgpo.get()
                    now = datetime.datetime.utcnow()
                    head_block_time = datetime.datetime.strptime(dgpo["time"], "%Y-%m-%dT%H:%M:%S")
                    join_head = int((now - head_block_time).total_seconds()) // STEEM_BLOCK_INTERVAL
                    
                    if join_head > STEEM_BLOCK_INTERVAL:
                        generate_blocks(steemd, {"count": join_head}, cached_dgpo=cached_dgpo, produce_realtime=produce_realtime)
                        cached_dgpo.reset()
                else:
                    transactions_per_block = metadata.get("txgen:transactions_per_block", transactions_per_block)
                    semver = metadata.get("txgen:semver", '0.0')
                    major_version, minor_version = semver.split('.')
                    major_version = int(major_version)
                    minor_version = int(minor_version)

                    if major_version == ACTIONS_MAJOR_VERSION_SUPPORTED:
                        print("metadata:", metadata)
                    else:
                        raise RuntimeError("Unsupported actions:", metadata)
                        
                    if minor_version < ACTIONS_MINOR_VERSION_SUPPORTED:
                        print("WARNING: Older actions encountered.", file=sys.stderr)
            elif cmd == "wait_blocks":
                if metadata and args.get("count") == 1 and args.get("miss_blocks"):
                    if args["miss_blocks"] < metadata["recommend:miss_blocks"]:
                        args["miss_blocks"] = metadata["recommend:miss_blocks"]
                generate_blocks(steemd, args, cached_dgpo=cached_dgpo, produce_realtime=produce_realtime)
                cached_dgpo.reset()
            elif cmd == "submit_transaction":
                tx = args["tx"]
                dgpo = cached_dgpo.get()
                tx["ref_block_num"] = dgpo["head_block_number"] & 0xFFFF
                tx["ref_block_prefix"] = struct.unpack_from("<I", unhexlify(dgpo["head_block_id"]), 4)[0]
                head_block_time = datetime.datetime.strptime(dgpo["time"], "%Y-%m-%dT%H:%M:%S")
                expiration = head_block_time+datetime.timedelta(minutes=1)
                expiration_str = expiration.strftime("%Y-%m-%dT%H:%M:%S")
                tx["expiration"] = expiration_str

                wif_sigs = tx["wif_sigs"]
                del tx["wif_sigs"]

                sigs = []
                for wif in wif_sigs:
                    if not isinstance(wif_sigs, list):
                        raise RuntimeError("wif_sigs is not list")
                    result = signer.sign_transaction(tx, wif)
                    if "error" in result:
                        print("could not sign transaction", tx, "due to error:", result["error"])
                    else:
                        sigs.append(result["result"]["sig"])
                tx["signatures"] = sigs
                print("bcast:", json.dumps(tx, separators=(",", ":")))

                steemd.network_broadcast_api.broadcast_transaction(trx=tx)
                transactions_count += 1
        except Exception as e:
            fail_file.write(json.dumps([cmd, args, str(e)])+"\n")
            fail_file.flush()
            if die_on_fail:
                raise
        
        if metadata and transactions_count > 0 and transactions_count % transactions_per_block == 0:
            generate_blocks(steemd, {"count": 1}, cached_dgpo=cached_dgpo, produce_realtime=produce_realtime)
            cached_dgpo.reset()
            if cmd == "wait_blocks" and args.get("count") == 1 and not args.get("miss_blocks"):
                continue