예제 #1
0
    def get_info(chain):
        max_height = 0
        for coin in chain.coins.all():
            rpc, message = send_rpc(
                {
                    'method': 'getinfo',
                    'params': []
                },
                schema_name=chain.schema_name,
                rpc_port=coin.rpc_port
            )

            if not rpc:
                continue

            info = Info.objects.create(
                unit=rpc['walletunit'],
                max_height=rpc['blocks'],
                money_supply=rpc['moneysupply'],
                total_parked=rpc.get('totalparked'),
                connections=rpc['connections'],
                difficulty=rpc['difficulty'],
                pay_tx_fee=rpc['paytxfee'],
            )

            logger.info('saved {}'.format(info))

            max_height = info.max_height

        return max_height
예제 #2
0
    def post(request):
        raw_tx = request.POST.get('hex')

        if not raw_tx:
            return JsonResponse(
                {
                    'status': 'failure',
                    'data': 'No raw Tx was sent'
                }
            )

        rpc, message = send_rpc(
            {
                'method': 'sendrawtransaction',
                'params': [raw_tx, 1],
            },
            schema_name=connection.tenant.schema_name
        )

        if not rpc:
            return JsonResponse(
                {
                    'status': 'failure',
                    'data': 'Daemon reported an error: {}'.format(message)
                }
            )

        return JsonResponse(
            {
                'status': 'success',
                'data': rpc
            }
        )
예제 #3
0
def get_info(chain):
    with schema_context(chain):
        for coin in Coin.objects.filter(chain__schema_name=chain):
            logger.info(f"getting info for coin {coin} on chain {chain}")
            rpc, message = send_rpc(
                {
                    "method": "getinfo",
                    "params": []
                },
                schema_name=connection.schema_name,
                rpc_port=coin.rpc_port,
            )

            if not rpc:
                continue

            info = Info.objects.create(
                unit=rpc["walletunit"],
                max_height=rpc["blocks"],
                money_supply=rpc["moneysupply"],
                total_parked=rpc.get("totalparked"),
                connections=rpc["connections"],
                difficulty=rpc["difficulty"],
                pay_tx_fee=rpc["paytxfee"],
            )

            logger.info(f"saved {info} for coin {coin}")

        Channel("display_info").send({"chain": connection.schema_name})
예제 #4
0
    def handle(self, *args, **options):
        """
        Parse the block chain
        """
        chain = connection.tenant
        rpc = send_rpc(
            {
                'method': 'getinfo',
                'params': []
            },
            schema_name=chain.schema_name
        )

        if not rpc:
            logger.error('no RPC connection')
            return

        max_height = rpc['blocks']

        for height in range(max_height):
            try:
                block = Block.objects.get(height=height)
                logger.info('existing block {}'.format(block))
                continue
            except Block.DoesNotExist:
                block_hash = get_block_hash(height=height, schema_name=chain.schema_name)
                if not block_hash:
                    continue
                block = Block(hash=block_hash)
                try:
                    block.save(validate=False)
                except IntegrityError:
                    pre_block = Block.objects.get(hash=block_hash)
                    if pre_block.height:
                        logger.error(
                            'block with hash {} already exists: {}'.format(
                                block_hash[:8],
                                pre_block
                            )
                        )
                        continue
                    pre_block.height = height
                    pre_block.save(validate=False)
                    logger.info('updated block {}'.format(pre_block))
                    continue

                logger.info('created block {}'.format(block))

        for block in Block.objects.all().order_by('height'):
            block.save()

            logger.info('saved block {}'.format(block))
예제 #5
0
    def get_highest_blocks(chain, max_height):
        current_highest_block = Block.objects.all().aggregate(
            Max('height')
        ).get(
            'height__max'
        )

        if not current_highest_block:
            current_highest_block = -1

        while max_height > current_highest_block:
            current_highest_block += 1
            rpc_hash, message = send_rpc(
                {
                    'method': 'getblockhash',
                    'params': [current_highest_block]
                },
                schema_name=chain.schema_name
            )
            if rpc_hash:
                block, _ = Block.objects.get_or_create(hash=rpc_hash)
                logger.info('saved block {}'.format(block))

        # give a short amount of time for the block(s) to be saved
        sleep(5)

        top_blocks = Block.objects.exclude(height=None).order_by('-height')[:50]
        index = 0

        for block in top_blocks:
            Group('{}_latest_blocks_list'.format(connection.schema_name)).send(
                {
                    'text': json.dumps(
                        {
                            'message_type': 'update_block',
                            'index': index,
                            'block_html': render_to_string(
                                'explorer/fragments/block.html',
                                {
                                    'block': block
                                }
                            ),
                            'block_is_valid': block.is_valid
                        }
                    )
                }
            )
            index += 1
예제 #6
0
    def get_peer_info(chain):
        rpc, msg = send_rpc(
            {
                'method': 'getpeerinfo',
                'params': []
            },
            schema_name=chain.schema_name,
        )

        if not rpc:
            return

        for peer_info in rpc:
            address = peer_info.get('addr')

            if not address:
                continue

            address_part = address.split(':')
            last_send = make_aware(
                datetime.datetime.fromtimestamp(peer_info.get('lastsend', 0))
            )
            last_receive = make_aware(
                datetime.datetime.fromtimestamp(peer_info.get('lastrecv', 0))
            )
            connection_time = make_aware(
                datetime.datetime.fromtimestamp(peer_info.get('conntime', 0))
            )

            peer, _ = Peer.objects.update_or_create(
                address=address_part[0],
                defaults={
                    'port': address_part[1],
                    'services': peer_info.get('services'),
                    'last_send': last_send,
                    'last_receive': last_receive,
                    'connection_time': connection_time,
                    'version': peer_info.get('version'),
                    'sub_version': peer_info.get('subver'),
                    'inbound': peer_info.get('inbound'),
                    'release_time': peer_info.get('releasetime'),
                    'height': peer_info.get('height'),
                    'ban_score': peer_info.get('banscore'),
                }
            )

            logger.info('saved peer {}'.format(peer))
예제 #7
0
def fix_block_park_rates(block, chain):
    logger.info('fixing park rates on block {}'.format(block))
    rpc, msg = send_rpc(
        {
            'method': 'getblock',
            'params': [block.hash],
        },
        schema_name=chain
    )

    if not rpc:
        return False

    park_rates = rpc.get('parkrates', [])
    block.parse_rpc_parkrates(park_rates)
    block.park_rates = park_rates
    block.save()
예제 #8
0
def fix_block_votes(block, chain):
    logger.info('fixing votes on block {}'.format(block))
    rpc, msg = send_rpc(
        {
            'method': 'getblock',
            'params': [block.hash],
        },
        schema_name=chain
    )

    if not rpc:
        return False

    vote = rpc.get('vote', {})
    block.parse_rpc_votes(vote)
    block.vote = vote
    block.save()
예제 #9
0
def get_peer_info(chain):
    with schema_context(chain):
        rpc, msg = send_rpc(
            {
                "method": "getpeerinfo",
                "params": []
            },
            schema_name=connection.schema_name,
        )

        if not rpc:
            return

        for peer_info in rpc:
            address = peer_info.get("addr")

            if not address:
                continue

            address_part = address.split(":")
            last_send = make_aware(
                datetime.datetime.fromtimestamp(peer_info.get("lastsend", 0)))
            last_receive = make_aware(
                datetime.datetime.fromtimestamp(peer_info.get("lastrecv", 0)))
            connection_time = make_aware(
                datetime.datetime.fromtimestamp(peer_info.get("conntime", 0)))

            peer, _ = Peer.objects.update_or_create(
                address=address_part[0],
                defaults={
                    "port": address_part[1],
                    "services": peer_info.get("services"),
                    "last_send": last_send,
                    "last_receive": last_receive,
                    "connection_time": connection_time,
                    "version": peer_info.get("version"),
                    "sub_version": peer_info.get("subver"),
                    "inbound": peer_info.get("inbound"),
                    "release_time": peer_info.get("releasetime"),
                    "height": peer_info.get("height"),
                    "ban_score": peer_info.get("banscore"),
                },
            )

            logger.info("saved peer {}".format(peer))
예제 #10
0
def fix_merkle_root(block, chain):
    logger.info('fixing merkle root on block {}'.format(block))
    rpc, msg = send_rpc(
        {
            'method': 'getblock',
            'params': [block.hash],
        },
        schema_name=chain
    )

    if not rpc:
        return False

    transactions = rpc.get('tx', [])
    block_tx = block.transactions.all().values_list('tx_id', flat=True)

    # add missing transactions
    for tx_id in list(set(transactions) - set(block_tx)):
        logger.info('adding missing tx {} to {}'.format(tx_id[:8], block))
        tx, _ = Transaction.objects.get_or_create(
            tx_id=tx_id
        )
        logger.info('block = {}'.format(block))
        tx.block = block
        tx.save()

    # remove additional transactions
    for tx in block.transactions.all():
        if tx.tx_id not in transactions:
            logger.error('tx {} does not belong to block {}'.format(tx, block))
            tx.delete()
            continue

        # fix index
        rpc_index = transactions.index(tx.tx_id)
        if tx.index != rpc_index:
            logger.error(
                'incorrect index for tx {}: ({})'.format(tx, rpc_index)
            )
            tx.index = rpc_index

        tx.save()

    # reinitialise validation
    block.save()
예제 #11
0
    def get_data(schema, block):
        rpc_block, message = send_rpc(
            {"method": "getblock", "params": [block.hash, True, True]},
            schema_name=schema,
        )

        if not rpc_block:
            logger.warning("No data for {}: {}".format(block, message))
            return

        with schema_context(schema):

            # save the votes
            block.parse_rpc_votes(rpc_block.get("vote", {}))

            # save active park rates
            block.parse_rpc_parkrates(rpc_block.get("parkrates", []))

            logger.info("Saved data for {}".format(block))
예제 #12
0
    def post(request):
        raw_tx = request.POST.get("hex")

        if not raw_tx:
            return JsonResponse({"status": "failure", "data": "No raw Tx was sent"})

        rpc, message = send_rpc(
            {"method": "sendrawtransaction", "params": [raw_tx, 1],},
            schema_name=connection.tenant.schema_name,
        )

        if not rpc:
            return JsonResponse(
                {
                    "status": "failure",
                    "data": "Daemon reported an error: {}".format(message),
                }
            )

        return JsonResponse({"status": "success", "data": rpc})
예제 #13
0
파일: block.py 프로젝트: jooize/crypto-daio
def fix_merkle_root(block, chain):
    logger.info('fixing merkle root on block {}'.format(block))
    rpc = send_rpc({
        'method': 'getblock',
        'params': [block.hash],
    },
                   schema_name=chain)

    if not rpc:
        return False

    transactions = rpc.get('tx', [])
    block_tx = block.transactions.all().values_list('tx_id', flat=True)

    # add missing transactions
    for tx_id in list(set(transactions) - set(block_tx)):
        logger.info('adding missing tx {} to {}'.format(tx_id[:8], block))
        tx, _ = Transaction.objects.get_or_create(tx_id=tx_id)
        logger.info('block = {}'.format(block))
        tx.block = block
        tx.save()

    # remove additional transactions
    for tx in block.transactions.all():
        if tx.tx_id not in transactions:
            logger.error('tx {} does not belong to block {}'.format(tx, block))
            tx.delete()
            continue

        # fix index
        rpc_index = transactions.index(tx.tx_id)
        if tx.index != rpc_index:
            logger.error('incorrect index for tx {}: ({})'.format(
                tx, rpc_index))
            tx.index = rpc_index

        tx.save()

    # reinitialise validation
    block.save()
예제 #14
0
    def handle(self, *args, **options):
        """
        get the raw block 
        """
        if options['block']:
            blocks = Block.objects.filter(
                height=options['block']).order_by('height')
        else:
            # no block specified so validate all blocks starting from start_height
            blocks = Block.objects.filter(
                height__gte=options['start_height']).order_by('height')

        if options['limit']:
            blocks = blocks[:int(options['limit'])]

        logger.info('fetching raw blocks')

        # paginate to speed the initial load up a bit
        paginator = Paginator(blocks, 1000)

        for page_num in paginator.page_range:
            logger.info('parsing page {}'.format(page_num))
            for block in paginator.page(page_num):
                if block.height == 0:
                    continue

                # get the raw rpc block
                rpc_block = send_rpc(
                    {
                        'method': 'getblock',
                        'params': [block.hash, True, True]
                    },
                    schema_name=connection.schema_name,
                )

                for tx in rpc_block.get('tx', []):
                    try:
                        block_tx = block.transactions.get(tx_id=tx.get('txid'))
                    except Transaction.DoesNotExist:
                        logger.warning('transaction not found {}'.format(
                            tx.get('txid')[:8]))
                        block.save()
                        continue
                    for tout in tx.get('vout', []):
                        try:
                            block_tx_out = block_tx.outputs.get(
                                index=tout.get('n'))
                        except TxOutput.DoesNotExist:
                            logger.warning('output not found: {}'.format(
                                tout.get('n')))
                            block_tx.save()
                            continue
                        script = tout.get('scriptPubKey')
                        if not script:
                            continue
                        addresses = script.get('addresses')
                        if not addresses:
                            continue
                        address = addresses[0]
                        address_object, _ = Address.objects.get_or_create(
                            address=address)
                        if block_tx_out.address == address_object:
                            continue
                        block_tx_out.address = address_object
                        block_tx_out.save()
                        logger.info('added {} to {}'.format(
                            address, block_tx_out))

                    # check the tx is valid
                    valid, message = block_tx.validate()
                    if not valid:
                        logger.warning('{} is still invalid: {}'.format(
                            block_tx, message))
                        block_tx.save()

                # check the block is valid
                valid, message = block.validate()
                if not valid:
                    logger.warning('{} is still invalid: {}'.format(
                        block, message))
                    block.save()
예제 #15
0
    def handle(self, *args, **options):
        """
        Get the latest info from the coin daemon and send the messages to update the UI
        """
        chain = connection.tenant
        max_height = 0
        for coin in chain.coins.all():
            rpc = send_rpc(
                {
                    'method': 'getinfo',
                    'params': []
                },
                schema_name=chain.schema_name,
                rpc_port=coin.rpc_port
            )

            if not rpc:
                continue

            info = Info.objects.create(
                unit=rpc['walletunit'],
                max_height=rpc['blocks'],
                money_supply=rpc['moneysupply'],
                total_parked=rpc.get('totalparked'),
                connections=rpc['connections'],
                difficulty=rpc['difficulty'],
                pay_tx_fee=rpc['paytxfee'],
            )

            logger.info('saved {}'.format(info))

            max_height = info.max_height

        Channel('display_info').send({'chain': connection.schema_name})

        current_highest_block = Block.objects.all().aggregate(
            Max('height')
        ).get(
            'height__max'
        )

        while max_height > current_highest_block:
            current_highest_block += 1
            rpc_hash = send_rpc(
                {
                    'method': 'getblockhash',
                    'params': [current_highest_block]
                },
                schema_name=chain.schema_name
            )
            block, _ = Block.objects.get_or_create(hash=rpc_hash)
            logger.info('saved block {}'.format(block))

        # give a short amount of time for the block(s) to be saved
        sleep(5)

        top_blocks = Block.objects.exclude(height=None).order_by('-height')[:50]
        index = 0

        for block in top_blocks:
            block.save()
            Group('{}_latest_blocks_list'.format(connection.schema_name)).send(
                {
                    'text': json.dumps(
                        {
                            'message_type': 'update_block',
                            'index': index,
                            'block_html': render_to_string(
                                'explorer/fragments/block.html',
                                {
                                    'block': block
                                }
                            ),
                            'block_is_valid': block.is_valid
                        }
                    )
                }
            )
            index += 1
예제 #16
0
파일: block.py 프로젝트: jooize/crypto-daio
def repair_block(message):
    """
    Repair an existing block
    
    :param message: asgi valid message containing:
        block_hash (required) string - hash of block to repair
    """
    with schema_context(message.get('chain')):
        block_hash = message.get('block_hash')

        if not block_hash:
            logger.error('no block hash in message')
            return

        try:
            block = Block.objects.get(hash=block_hash)
        except Block.DoesNotExist:
            logger.error('no block found for hash {}'.format(block_hash[:7]))
            return

        valid, error_message = block.validate()
        if valid:
            logger.info('block {} is valid'.format(block))
            return

        logger.info('repairing block {}: {}'.format(block, error_message))

        # merkle root error means missing, extra or duplicate transactions
        if error_message == 'merkle root incorrect':
            fix_merkle_root(block, message.get('chain'))
            return

        if error_message == 'incorrect tx indexing':
            fix_merkle_root(block, message.get('chain'))
            return

        if error_message in [
                'missing attribute: self.previous_block',
                'no previous block hash', 'incorrect previous height',
                'no previous block hash'
        ]:
            fix_previous_block(block, message.get('chain'))
            return

        if error_message in [
                'incorrect next height',
                'next block does not lead on from this block',
                'missing next block'
        ]:
            fix_next_block(block, message.get('chain'))
            return

        # all other errors with the block can be solved by re-parsing it
        logger.info('re-parsing {}'.format(block))
        rpc = send_rpc(
            {
                'method': 'getblock',
                'params': [block_hash, True, True]
            },
            schema_name=message.get('chain'))
        if not rpc:
            return False
        # parse the block to save it
        block.parse_rpc_block(rpc)
예제 #17
0
def repair_transaction(message):
    """
    repair the given transaction
    :param message: 
    :return: 
    """
    with schema_context(message.get('chain')):
        tx_id = message.get('tx_id')
        if not tx_id:
            logger.error('no tx_id passed')

        # get the raw transaction
        rpc_tx = send_rpc({
            'method': 'getrawtransaction',
            'params': [tx_id, 1]
        },
                          schema_name=message.get('chain'))
        if not rpc_tx:
            return

        block_hash = rpc_tx.get('blockhash')
        if not block_hash:
            logger.error('no block hash found in rpc_tx: {}'.format(tx_id[:8]))
            # indicates that block is orphaned?
            return

        block, block_created = Block.objects.get_or_create(hash=block_hash)
        if block_created:
            # save has triggered validation which will parse the full block with tx
            logger.warning('block {} is new when parsing tx {}'.format(
                block, tx_id))
            return

        # get the block too for the index
        rpc_block = send_rpc({
            'method': 'getblock',
            'params': [block_hash]
        },
                             schema_name=message.get('chain'))

        if not rpc_block:
            return

        tx_list = rpc_block.get('tx', [])
        if not tx_list:
            logger.error('problem getting tx_list from block {}'.format(block))
            return

        tx_index = tx_list.index(tx_id)

        try:
            tx = Transaction.objects.get(tx_id=tx_id)
        except Transaction.DoesNotExist:
            logger.warning('tx {} is new.'.format(tx_id[:8]))
            tx = Transaction(tx_id=tx_id, block=block, index=tx_index)
            tx.save(validate=False)

        logger.info('repairing tx {}'.format(tx))

        valid, error_message = tx.validate()

        if valid:
            logger.info('tx {} is valid'.format(tx))
            return

        logger.error('tx {} invalid: {}'.format(tx, error_message))

        if error_message == 'incorrect index':
            tx.index = tx_index
            tx.save()
            logger.info('updated index of {}'.format(tx))
            return

        if error_message == 'no block':
            tx.block = block
            tx.save()
            logger.info('update block on {}'.format(tx))
            return

        if error_message == 'output has no address':
            for tout in rpc_tx.get('vout', []):
                try:
                    tx_out = tx.outputs.get(index=tout.get('n'))
                except TxOutput.DoesNotExist:
                    logger.warning('output not found: {}'.format(
                        tout.get('n')))
                    tx.save()
                    continue

                script = tout.get('scriptPubKey')
                if not script:
                    logger.warning(
                        'no script found in rpc for output {}'.format(tx_out))
                    continue

                if script.get('type') == 'park':
                    logger.info('park output')
                    park_data = script.get('park', {})
                    tx_out.park_duration = park_data.get('duration')
                    address = park_data.get('unparkaddress')
                else:
                    addresses = script.get('addresses', [])
                    if not addresses:
                        logger.warning(
                            'no addresses found in rpc for output {}'.format(
                                tx_out))
                        continue
                    address = addresses[0]

                address_object, _ = Address.objects.get_or_create(
                    address=address)
                if tx_out.address == address_object:
                    logger.info('output {} already has address {}'.format(
                        tx_out, address))
                    continue
                tx_out.address = address_object
                # update the value too
                tx_out.value = convert_to_satoshis(tout.get('value', 0.0))
                tx_out.save()
                logger.info('added {} to {}'.format(address, tx_out))
            return

        if error_message == 'address missing from previous output' \
                or error_message == 'previous output value is 0':
            scanned_transactions = []
            for tx_in in tx.inputs.all():
                if tx_in.previous_output:
                    if not tx_in.previous_output.address:
                        previous_tx_id = tx_in.previous_output.transaction.tx_id

                        if previous_tx_id in scanned_transactions:
                            continue

                        rpc_prev_tx = send_rpc(
                            {
                                'method': 'getrawtransaction',
                                'params': [previous_tx_id, 1]
                            },
                            schema_name=message.get('chain'))

                        for tout in rpc_prev_tx.get('vout', []):
                            if tout.get('n') != tx_in.previous_output.index:
                                continue
                            script = tout.get('scriptPubKey')

                            if not script:
                                logger.warning(
                                    'no script found in rpc for output {}'.
                                    format(tx_in.previous_output))
                                continue

                            if script.get('type') == 'park':
                                logger.info('park output')
                                park_data = script.get('park', {})
                                tx_in.previous_output.park_duration = park_data.get(
                                    'duration')  # noqa
                                address = park_data.get('unparkaddress')
                            else:
                                addresses = script.get('addresses', [])
                                if not addresses:
                                    logger.warning(
                                        'no addresses found in rpc for output {}'
                                        .format(tx_in.previous_output))
                                    continue
                                address = addresses[0]

                            address_object, _ = Address.objects.get_or_create(
                                address=address)

                            if tx_in.previous_output.address == address_object:
                                logger.info(
                                    'output {} already has address {}'.format(
                                        tx_in.previous_output, address))
                                continue
                            tx_in.previous_output.address = address_object
                            # update the value too
                            tx_in.previous_output.value = convert_to_satoshis(
                                tout.get('value', 0.0))
                            tx_in.previous_output.save()
                            logger.info('added {} to {}'.format(
                                address, tx_in.previous_output))
                            # re-validate transaction too
                            tx_in.previous_output.transaction.save()

                        scanned_transactions.append(previous_tx_id)
            return

        if error_message == 'park output has no duration':
            for tout in rpc_tx.get('vout', []):
                try:
                    tx_out = tx.outputs.get(index=tout.get('n'))
                except TxOutput.DoesNotExist:
                    logger.warning('output not found: {}'.format(
                        tout.get('n')))
                    tx.save()
                    continue

                script = tout.get('scriptPubKey')
                if not script:
                    logger.warning(
                        'no script found in rpc for output {}'.format(tx_out))
                    continue

                if script.get('type') != 'park':
                    continue

                park_data = script.get('park', {})
                tx_out.park_duration = park_data.get('duration')
                address = park_data.get('unparkaddress')
                address_object, _ = Address.objects.get_or_create(
                    address=address)
                tx_out.address = address_object
                tx_out.save()
                logger.info('added park data to {}'.format(tx_out))

        tx.parse_rpc_tx(rpc_tx)
예제 #18
0
def repair_transaction(message):
    """
    repair the given transaction
    :param message: 
    :return: 
    """
    with schema_context(message.get('chain')):
        tx_id = message.get('tx_id')
        if not tx_id:
            logger.error('no tx_id passed')

        # get the raw transaction
        rpc_tx, msg = send_rpc(
            {
                'method': 'getrawtransaction',
                'params': [tx_id, 1]
            },
            schema_name=message.get('chain')
        )
        if not rpc_tx:
            return

        block_hash = rpc_tx.get('blockhash')
        if not block_hash:
            logger.error('no block hash found in rpc for tx {}'.format(tx_id[:8]))
            # indicates that block is orphaned?
            # get the transaction to get the block it is attached to
            try:
                tx = Transaction.objects.get(tx_id=tx_id)
            except Transaction.DoesNotExist:
                logger.warning('no existing tx with id {}'.format(tx_id[:8]))
                return

            if not tx.block:
                logger.warning('tx {} has no block'.format(tx_id[:8]))

            # get the current height of this block
            block_height = tx.block.height

            # then delete the block
            tx.block.delete()

            # get the block hash of the actual block at this height
            block_hash = get_block_hash(block_height, message.get('chain'))

        block, block_created = Block.objects.get_or_create(hash=block_hash)
        if block_created:
            # save has triggered validation which will parse the full block with tx
            logger.warning('block {} is new when parsing tx {}'.format(block, tx_id))
            return

        # get the block too for the index
        rpc_block, msg = send_rpc(
            {
                'method': 'getblock',
                'params': [block_hash]
            },
            schema_name=message.get('chain')
        )

        if not rpc_block:
            return

        tx_list = rpc_block.get('tx', [])
        if not tx_list:
            logger.error('problem getting tx_list from block {}'.format(block))
            return

        tx_index = tx_list.index(tx_id)

        try:
            tx = Transaction.objects.get(tx_id=tx_id)
        except Transaction.DoesNotExist:
            logger.warning('tx {} is new.'.format(tx_id[:8]))
            tx = Transaction(tx_id=tx_id, block=block, index=tx_index)
            tx.save(validate=False)

        logger.info('repairing tx {}'.format(tx))

        valid, error_message = tx.validate()

        if valid:
            logger.info('tx {} is valid'.format(tx))
            return

        logger.error('tx {} invalid: {}'.format(tx, error_message))

        if error_message == 'incorrect index':
            tx.index = tx_index
            tx.save()
            logger.info('updated index of {}'.format(tx))
            return

        if error_message == 'no block':
            tx.block = block
            tx.save()
            logger.info('update block on {}'.format(tx))
            return

        if error_message == 'output has no address':
            for tout in rpc_tx.get('vout', []):
                try:
                    tx_out = tx.outputs.get(index=tout.get('n'))
                except TxOutput.DoesNotExist:
                    logger.warning('output not found: {}'.format(tout.get('n')))
                    tx.save()
                    continue

                script = tout.get('scriptPubKey')
                if not script:
                    logger.warning(
                        'no script found in rpc for output {}'.format(tx_out)
                    )
                    continue

                if script.get('type') == 'park':
                    logger.info('park output')
                    park_data = script.get('park', {})
                    tx_out.park_duration = park_data.get('duration')
                    address = park_data.get('unparkaddress')
                else:
                    addresses = script.get('addresses', [])
                    if not addresses:
                        logger.warning(
                            'no addresses found in rpc for output {}'.format(tx_out)
                        )
                        continue
                    address = addresses[0]

                address_object, _ = Address.objects.get_or_create(address=address)
                if tx_out.address == address_object:
                    logger.info(
                        'output {} already has address {}'.format(tx_out, address)
                    )
                    continue
                tx_out.address = address_object
                # update the value too
                tx_out.value = convert_to_satoshis(tout.get('value', 0.0))
                tx_out.save()
                logger.info('added {} to {}'.format(address, tx_out))
            return

        if error_message == 'address missing from previous output' \
                or error_message == 'previous output value is 0':
            scanned_transactions = []
            for tx_in in tx.inputs.all():
                if tx_in.previous_output:
                    if not tx_in.previous_output.address:
                        previous_tx_id = tx_in.previous_output.transaction.tx_id

                        if previous_tx_id in scanned_transactions:
                            continue

                        rpc_prev_tx, msg = send_rpc(
                            {
                                'method': 'getrawtransaction',
                                'params': [previous_tx_id, 1]
                            },
                            schema_name=message.get('chain')
                        )

                        for tout in rpc_prev_tx.get('vout', []):
                            if tout.get('n') != tx_in.previous_output.index:
                                continue
                            script = tout.get('scriptPubKey')

                            if not script:
                                logger.warning(
                                    'no script found in rpc for output {}'.format(
                                        tx_in.previous_output
                                    )
                                )
                                continue

                            if script.get('type') == 'park':
                                logger.info('park output')
                                park_data = script.get('park', {})
                                tx_in.previous_output.park_duration = park_data.get('duration')  # noqa
                                address = park_data.get('unparkaddress')
                            else:
                                addresses = script.get('addresses', [])
                                if not addresses:
                                    logger.warning(
                                        'no addresses found in rpc for output {}'.format(
                                            tx_in.previous_output
                                        )
                                    )
                                    continue
                                address = addresses[0]

                            address_object, _ = Address.objects.get_or_create(
                                address=address
                            )

                            if tx_in.previous_output.address == address_object:
                                logger.info(
                                    'output {} already has address {}'.format(
                                        tx_in.previous_output,
                                        address
                                    )
                                )
                                continue
                            tx_in.previous_output.address = address_object
                            # update the value too
                            tx_in.previous_output.value = convert_to_satoshis(
                                tout.get('value', 0.0)
                            )
                            tx_in.previous_output.save()
                            logger.info(
                                'added {} to {}'.format(address, tx_in.previous_output)
                            )
                            # re-validate transaction too
                            tx_in.previous_output.transaction.save()

                        scanned_transactions.append(previous_tx_id)
            return

        if error_message == 'park output has no duration':
            for tout in rpc_tx.get('vout', []):
                try:
                    tx_out = tx.outputs.get(index=tout.get('n'))
                except TxOutput.DoesNotExist:
                    logger.warning('output not found: {}'.format(tout.get('n')))
                    tx.save()
                    continue

                script = tout.get('scriptPubKey')
                if not script:
                    logger.warning(
                        'no script found in rpc for output {}'.format(tx_out)
                    )
                    continue

                if script.get('type') != 'park':
                    continue

                park_data = script.get('park', {})
                tx_out.park_duration = park_data.get('duration')
                address = park_data.get('unparkaddress')
                address_object, _ = Address.objects.get_or_create(address=address)
                tx_out.address = address_object
                tx_out.save()
                logger.info('added park data to {}'.format(tx_out))

        tx.parse_rpc_tx(rpc_tx)
예제 #19
0
def repair_block(message):
    """
    Repair an existing block
    
    :param message: asgi valid message containing:
        block_hash (required) string - hash of block to repair
    """
    with schema_context(message.get('chain')):
        block_hash = message.get('block_hash')

        if not block_hash:
            logger.error('no block hash in message')
            return

        try:
            block = Block.objects.get(hash=block_hash)
        except Block.DoesNotExist:
            logger.error('no block found for hash {}'.format(block_hash[:7]))
            return

        valid, error_message = block.validate()
        if valid:
            logger.info('block {} is valid'.format(block))
            return

        logger.info('repairing block {}: {}'.format(block, error_message))

        # merkle root error means missing, extra or duplicate transactions
        if error_message == 'merkle root incorrect':
            fix_merkle_root(block, message.get('chain'))
            return

        if error_message == 'incorrect tx indexing':
            fix_merkle_root(block, message.get('chain'))
            return

        if error_message in ['missing attribute: self.previous_block',
                             'no previous block hash',
                             'incorrect previous height',
                             'no previous block hash']:
            fix_previous_block(block, message.get('chain'))
            return

        if error_message in ['incorrect next height',
                             'next block does not lead on from this block',
                             'missing next block']:
            fix_next_block(block, message.get('chain'))
            return

        if error_message in ['custodian votes do not match',
                             'park rate votes do not match',
                             'motion votes do not match',
                             'fee votes do not match']:
            fix_block_votes(block, message.get('chain'))
            return

        if error_message == 'active park rates do not match':
            fix_block_park_rates(block, message.get('chain'))
            return

        # all other errors with the block can be solved by re-parsing it
        logger.info('re-parsing {}'.format(block))
        rpc, msg = send_rpc(
            {
                'method': 'getblock',
                'params': [block_hash, True, True]
            },
            schema_name=message.get('chain')
        )
        if not rpc:
            return False
        # parse the block to save it
        block.parse_rpc_block(rpc)
예제 #20
0
    def handle(self, *args, **options):
        """
        get the raw block 
        """
        if options['block']:
            blocks = Block.objects.filter(height=options['block']).order_by('height')
        else:
            # no block specified so validate all blocks starting from start_height
            blocks = Block.objects.filter(
                height__gte=options['start_height']
            ).order_by(
                'height'
            )

        if options['limit']:
            blocks = blocks[:int(options['limit'])]

        logger.info('fetching raw blocks')

        # paginate to speed the initial load up a bit
        paginator = Paginator(blocks, 1000)

        for page_num in paginator.page_range:
            logger.info('parsing page {}'.format(page_num))
            for block in paginator.page(page_num):
                if block.height == 0:
                    continue

                # get the raw rpc block
                rpc_block, msg = send_rpc(
                    {
                        'method': 'getblock',
                        'params': [block.hash, True, True]
                    },
                    schema_name=connection.schema_name,
                )

                for tx in rpc_block.get('tx', []):
                    try:
                        block_tx = block.transactions.get(tx_id=tx.get('txid'))
                    except Transaction.DoesNotExist:
                        logger.warning(
                            'transaction not found {}'.format(tx.get('txid')[:8])
                        )
                        block.save()
                        continue
                    for tout in tx.get('vout', []):
                        try:
                            block_tx_out = block_tx.outputs.get(index=tout.get('n'))
                        except TxOutput.DoesNotExist:
                            logger.warning('output not found: {}'.format(tout.get('n')))
                            block_tx.save()
                            continue
                        script = tout.get('scriptPubKey')
                        if not script:
                            continue
                        addresses = script.get('addresses')
                        if not addresses:
                            continue
                        address = addresses[0]
                        address_object, _ = Address.objects.get_or_create(address=address)
                        if block_tx_out.address == address_object:
                            continue
                        block_tx_out.address = address_object
                        block_tx_out.save()
                        logger.info('added {} to {}'.format(address, block_tx_out))

                    # check the tx is valid
                    valid, message = block_tx.validate()
                    if not valid:
                        logger.warning(
                            '{} is still invalid: {}'.format(block_tx, message)
                        )
                        block_tx.save()

                # check the block is valid
                valid, message = block.validate()
                if not valid:
                    logger.warning(
                        '{} is still invalid: {}'.format(block, message)
                    )
                    block.save()