Beispiel #1
0
def api_service_node_stats():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    stakinginfo = FutureJSON(lmq, oxend, 'rpc.get_staking_requirement', 30)
    sns = get_sns_future(lmq, oxend)
    sns = sns.get()
    if 'service_node_states' not in sns:
        return flask.jsonify({"status": "Error retrieving SN stats"}), 500
    sns = sns['service_node_states']

    stats = {'active': 0, 'funded': 0, 'awaiting_contribution': 0, 'decommissioned': 0, 'staked': 0}
    for sn in sns:
        if sn['funded']:
            stats['funded'] += 1
            if sn['active']:
                stats['active'] += 1
            else:
                stats['decommissioned'] += 1
        else:
            stats['awaiting_contribution'] += 1
        stats['staked'] += sn['total_contributed']

    stats['staked'] /= 1_000_000_000
    stats['sn_reward'] = 16.5
    stats['sn_reward_interval'] = stats['active']
    stakinginfo = stakinginfo.get()
    stats['sn_staking_requirement_full'] = stakinginfo['staking_requirement'] / 1_000_000_000
    stats['sn_staking_requirement_min'] = stats['sn_staking_requirement_full'] / 4

    info = info.get()
    stats['height'] = info['height']
    return flask.jsonify({"data": stats, "status": "OK"})
Beispiel #2
0
def show_sn(pubkey):
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
    sn = sn_req(lmq, oxend, pubkey).get()

    if 'service_node_states' not in sn or not sn['service_node_states']:
        return flask.render_template(
            'not_found.html',
            info=info.get(),
            type='sn',
            id=pubkey,
        )

    sn = sn['service_node_states'][0]
    # These are a bit non-trivial to properly calculate:

    # Number of staked contributions
    sn['num_contributions'] = sum(
        len(x["locked_contributions"]) for x in sn["contributors"]
        if "locked_contributions" in x)
    # Number of unfilled, reserved contribution spots:
    sn['num_reserved_spots'] = sum(x["amount"] < x["reserved"]
                                   for x in sn["contributors"])
    # Available open contribution spots:
    sn['num_open_spots'] = 0 if sn['total_reserved'] >= sn[
        'staking_requirement'] else max(
            0, 4 - sn['num_contributions'] - sn['num_reserved_spots'])

    return flask.render_template(
        'sn.html',
        info=info.get(),
        hf=hfinfo.get(),
        sn=sn,
    )
Beispiel #3
0
def show_quorums():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    quos = get_quorums_future(lmq, oxend, info.get()['height'])

    return flask.render_template('quorums.html',
                                 info=info.get(),
                                 quorums=get_quorums(quos))
Beispiel #4
0
def api_networkinfo():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)

    info = info.get()
    data = {**info}
    hfinfo = hfinfo.get()
    data['current_hf_version'] = hfinfo['version']
    data['next_hf_height'] = hfinfo['earliest_height'] if 'earliest_height' in hfinfo else None
    return flask.jsonify({"data": data, "status": "OK"})
Beispiel #5
0
def show_block(height=None, hash=None, more_details=False):
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
    if height is not None:
        val = height
    elif hash is not None:
        val = hash

    block = None if val is None else block_with_txs_req(lmq, oxend, val).get()
    if block is None:
        return flask.render_template("not_found.html",
                                     info=info.get(),
                                     hfinfo=hfinfo.get(),
                                     type='block',
                                     height=height,
                                     id=hash)

    next_block = None
    block_height = block['block_header']['height']
    txs = get_block_txs_future(lmq, oxend, block)

    if info.get()['height'] > 1 + block_height:
        next_block = block_header_req(lmq, oxend,
                                      '{}'.format(block_height + 1))

    if more_details:
        formatter = HtmlFormatter(cssclass="syntax-highlight", style="native")
        more_details = {
            'details_css':
            formatter.get_style_defs('.syntax-highlight'),
            'details_html':
            highlight(json.dumps(block, indent="\t", sort_keys=True),
                      JsonLexer(), formatter),
        }
    else:
        more_details = {}

    transactions = [] if txs is None else parse_txs(txs.get()).copy()
    miner_tx = transactions.pop() if transactions else []

    return flask.render_template(
        "block.html",
        info=info.get(),
        hfinfo=hfinfo.get(),
        block_header=block['block_header'],
        block=block,
        miner_tx=miner_tx,
        transactions=transactions,
        next_block=next_block.get() if next_block else None,
        **more_details,
    )
Beispiel #6
0
def api_emission():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    coinbase = FutureJSON(lmq,
                          oxend,
                          'admin.get_coinbase_tx_sum',
                          10,
                          timeout=1,
                          fail_okay=True,
                          args={
                              "height": 0,
                              "count": 2**31 - 1
                          }).get()
    if not coinbase:
        return flask.jsonify(None)
    info = info.get()
    return flask.jsonify({
        "data": {
            "blk_no":
            info['height'] - 1,
            "burn":
            coinbase["burn_amount"],
            "circulating_supply":
            coinbase["emission_amount"] - coinbase["burn_amount"],
            "coinbase":
            coinbase["emission_amount"] - coinbase["burn_amount"],
            "emission":
            coinbase["emission_amount"],
            "fee":
            coinbase["fee_amount"]
        },
        "status": "success"
    })
Beispiel #7
0
def mempool():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    mempool = get_mempool_future(lmq, oxend)

    return flask.render_template('mempool.html',
            info=info.get(),
            mempool=parse_mempool(mempool),
            )
Beispiel #8
0
def search():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    val = (flask.request.args.get('value') or '').strip()

    if val and len(val) < 10 and val.isdigit(): # Block height
        return flask.redirect(flask.url_for('show_block', height=val), code=301)

    if val and len(val) == 58 and val.endswith(".snode") and val[51] in 'yoYO' and all(c in base32z_dict for c in val[0:52].lower()):
        v, bits = 0, 0
        for x in val[0:52].lower():
            v = (v << 5) | base32z_map[x]  # Arbitrary precision integers hurray!
        # The above loads 260 bytes (5 bits per char * 52 chars), but we only want 256:
        v >>= 4
        val = "{:64x}".format(v)

    elif not val or len(val) != 64 or any(c not in string.hexdigits for c in val):
        return flask.render_template('not_found.html',
                info=info.get(),
                type='bad_search',
                id=val,
                )

    # Initiate all the lookups at once, then redirect to whichever one responds affirmatively
    snreq = sn_req(lmq, oxend, val)
    blreq = block_header_req(lmq, oxend, val, fail_okay=True)
    txreq = tx_req(lmq, oxend, [val])

    sn = snreq.get()
    if sn and 'service_node_states' in sn and sn['service_node_states']:
        return flask.redirect(flask.url_for('show_sn', pubkey=val), code=301)
    bl = blreq.get()
    if bl and 'block_header' in bl and bl['block_header']:
        return flask.redirect(flask.url_for('show_block', hash=val), code=301)
    tx = txreq.get()
    if tx and 'txs' in tx and tx['txs']:
        return flask.redirect(flask.url_for('show_tx', txid=val), code=301)

    return flask.render_template('not_found.html',
            info=info.get(),
            type='search',
            id=val,
            )
Beispiel #9
0
def sns():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    awaiting, active, inactive = get_sns(get_sns_future(lmq, oxend), info)

    return flask.render_template('service_nodes.html',
        info=info.get(),
        active_sns=active,
        awaiting_sns=awaiting,
        inactive_sns=inactive,
        )
Beispiel #10
0
def search():
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    val = (flask.request.args.get('value') or '').strip()

    if val and len(val) < 10 and val.isdigit():  # Block height
        return flask.redirect(flask.url_for('show_block', height=val),
                              code=301)

    if not val or len(val) != 64 or any(c not in string.hexdigits
                                        for c in val):
        return flask.render_template(
            'not_found.html',
            info=info.get(),
            type='bad_search',
            id=val,
        )

    # Initiate all the lookups at once, then redirect to whichever one responds affirmatively
    snreq = sn_req(lmq, oxend, val)
    blreq = block_header_req(lmq, oxend, val, fail_okay=True)
    txreq = tx_req(lmq, oxend, [val])

    sn = snreq.get()
    if 'service_node_states' in sn and sn['service_node_states']:
        return flask.redirect(flask.url_for('show_sn', pubkey=val), code=301)
    bl = blreq.get()
    if bl and 'block_header' in bl and bl['block_header']:
        return flask.redirect(flask.url_for('show_block', hash=val), code=301)
    tx = txreq.get()
    if tx and 'txs' in tx and tx['txs']:
        return flask.redirect(flask.url_for('show_tx', txid=val), code=301)

    return flask.render_template(
        'not_found.html',
        info=info.get(),
        type='search',
        id=val,
    )
Beispiel #11
0
def show_tx(txid, more_details=False):
    lmq, oxend = lmq_connection()
    info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    txs = tx_req(lmq, oxend, [txid]).get()

    if 'txs' not in txs or not txs['txs']:
        return flask.render_template(
            'not_found.html',
            info=info.get(),
            type='tx',
            id=txid,
        )
    tx = parse_txs(txs)[0]

    # If this is a state change, see if we have the quorum stored to provide context
    testing_quorum = None
    if tx['info']['version'] >= 4 and 'sn_state_change' in tx['extra']:
        testing_quorum = FutureJSON(
            lmq,
            oxend,
            'rpc.get_quorum_state',
            60,
            cache_key='tx_state_change',
            args={
                'quorum_type': 0,
                'start_height': tx['extra']['sn_state_change']['height']
            })

    kindex_info = {}  # { amount => { keyindex => {output-info} } }
    block_info_req = None
    if 'vin' in tx['info']:
        if len(tx['info']['vin']) == 1 and 'gen' in tx['info']['vin'][0]:
            tx['coinbase'] = True
        elif tx['info']['vin'] and config.enable_mixins_details:
            # Load output details for all outputs contained in the inputs
            outs_req = []
            for inp in tx['info']['vin']:
                # Key positions are stored as offsets from the previous index rather than indices,
                # so de-delta them back into indices:
                if 'key_offsets' in inp['key'] and 'key_indices' not in inp[
                        'key']:
                    kis = []
                    inp['key']['key_indices'] = kis
                    kbase = 0
                    for koff in inp['key']['key_offsets']:
                        kbase += koff
                        kis.append(kbase)
                    del inp['key']['key_offsets']

            outs_req = [{
                "amount": inp['key']['amount'],
                "index": ki
            } for inp in tx['info']['vin'] for ki in inp['key']['key_indices']]
            outputs = FutureJSON(lmq,
                                 oxend,
                                 'rpc.get_outs',
                                 args={
                                     'get_txid': True,
                                     'outputs': outs_req,
                                 }).get()
            if outputs and 'outs' in outputs and len(
                    outputs['outs']) == len(outs_req):
                outputs = outputs['outs']
                # Also load block details for all of those outputs:
                block_info_req = FutureJSON(
                    lmq,
                    oxend,
                    'rpc.get_block_header_by_height',
                    args={'heights': [o["height"] for o in outputs]})
                i = 0
                for inp in tx['info']['vin']:
                    amount = inp['key']['amount']
                    if amount not in kindex_info:
                        kindex_info[amount] = {}
                    ki = kindex_info[amount]
                    for ko in inp['key']['key_indices']:
                        ki[ko] = outputs[i]
                        i += 1

    if more_details:
        formatter = HtmlFormatter(cssclass="syntax-highlight",
                                  style="paraiso-dark")
        more_details = {
            'details_css':
            formatter.get_style_defs('.syntax-highlight'),
            'details_html':
            highlight(json.dumps(tx, indent="\t", sort_keys=True), JsonLexer(),
                      formatter),
        }
    else:
        more_details = {}

    block_info = {}  # { height => {block-info} }
    if block_info_req:
        bi = block_info_req.get()
        if 'block_headers' in bi:
            for bh in bi['block_headers']:
                block_info[bh['height']] = bh

    if testing_quorum:
        testing_quorum = testing_quorum.get()
    if testing_quorum:
        if 'quorums' in testing_quorum and testing_quorum['quorums']:
            testing_quorum = testing_quorum['quorums'][0]['quorum']
        else:
            testing_quorum = None

    return flask.render_template(
        'tx.html',
        info=info.get(),
        tx=tx,
        kindex_info=kindex_info,
        block_info=block_info,
        testing_quorum=testing_quorum,
        **more_details,
    )
Beispiel #12
0
def main(refresh=None, page=0, per_page=None, first=None, last=None):
    lmq, oxend = lmq_connection()
    inforeq = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
    stake = FutureJSON(lmq, oxend, 'rpc.get_staking_requirement', 10)
    base_fee = FutureJSON(lmq, oxend, 'rpc.get_fee_estimate', 10)
    hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
    mempool = get_mempool_future(lmq, oxend)
    sns = get_sns_future(lmq, oxend)
    checkpoints = FutureJSON(lmq,
                             oxend,
                             'rpc.get_checkpoints',
                             args={"count": 3})

    # This call is slow the first time it gets called in oxend but will be fast after that, so call
    # it with a very short timeout.  It's also an admin-only command, so will always fail if we're
    # using a restricted RPC interface.
    coinbase = FutureJSON(lmq,
                          oxend,
                          'admin.get_coinbase_tx_sum',
                          10,
                          timeout=1,
                          fail_okay=True,
                          args={
                              "height": 0,
                              "count": 2**31 - 1
                          })

    custom_per_page = ''
    if per_page is None or per_page <= 0 or per_page > config.max_blocks_per_page:
        per_page = config.blocks_per_page
    else:
        custom_per_page = '/{}'.format(per_page)

    # We have some chained request dependencies here and below, so get() them as needed; all other
    # non-dependent requests should already have a future initiated above so that they can
    # potentially run in parallel.
    info = inforeq.get()
    height = info['height']

    # Permalinked block range:
    if first is not None and last is not None and 0 <= first <= last and last <= first + 99:
        start_height, end_height = first, last
        if end_height - start_height + 1 != per_page:
            per_page = end_height - start_height + 1
            custom_per_page = '/{}'.format(per_page)
        # We generally can't get a perfect page number because our range (e.g. 5-14) won't line up
        # with pages (e.g. 10-19, 0-19), so just get as close as we can.  Next/Prev page won't be
        # quite right, but they'll be within half a page.
        page = round((height - 1 - end_height) / per_page)
    else:
        end_height = max(0, height - per_page * page - 1)
        start_height = max(0, end_height - per_page + 1)

    blocks = FutureJSON(lmq,
                        oxend,
                        'rpc.get_block_headers_range',
                        cache_key='main',
                        args={
                            'start_height': start_height,
                            'end_height': end_height,
                            'get_tx_hashes': True,
                        }).get()['headers']

    # If 'txs' is already there then it is probably left over from our cached previous call through
    # here.
    if blocks and 'txs' not in blocks[0]:
        txids = []
        for b in blocks:
            b['txs'] = []
            txids.append(b['miner_tx_hash'])
            if 'tx_hashes' in b:
                txids += b['tx_hashes']
        txs = parse_txs(tx_req(lmq, oxend, txids, cache_key='mempool').get())
        i = 0
        for tx in txs:
            # TXs should come back in the same order so we can just skip ahead one when the block
            # height changes rather than needing to search for the block
            if blocks[i]['height'] != tx['block_height']:
                i += 1
                while i < len(
                        blocks) and blocks[i]['height'] != tx['block_height']:
                    print("Something getting wrong: missing txes?",
                          file=sys.stderr)
                    i += 1
                if i >= len(blocks):
                    print("Something getting wrong: have leftover txes")
                    break
            blocks[i]['txs'].append(tx)

    # Clean up the SN data a bit to make things easier for the templates
    awaiting_sns, active_sns, inactive_sns = get_sns(sns, inforeq)

    return flask.render_template(
        'index.html',
        info=info,
        stake=stake.get(),
        fees=base_fee.get(),
        emission=coinbase.get(),
        hf=hfinfo.get(),
        active_sns=active_sns,
        inactive_sns=inactive_sns,
        awaiting_sns=awaiting_sns,
        blocks=blocks,
        block_size_median=statistics.median(b['block_size'] for b in blocks),
        page=page,
        per_page=per_page,
        custom_per_page=custom_per_page,
        mempool=parse_mempool(mempool),
        checkpoints=checkpoints.get(),
        refresh=refresh,
    )