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
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 } )
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})
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))
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
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))
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()
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()
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))
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()
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))
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})
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()
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()
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
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)
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)
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)
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)
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()