示例#1
0
def parse_trade_book(msg, msg_data):
    #book trades
    if (msg['category'] == 'order_matches'
        and ((msg['command'] == 'update' and msg_data['status'] == 'completed') #for a trade with BTC involved, but that is settled (completed)
             or ('forward_asset' in msg_data and msg_data['forward_asset'] != config.BTC and msg_data['backward_asset'] != config.BTC))): #or for a trade without BTC on either end

        if msg['command'] == 'update' and msg_data['status'] == 'completed':
            #an order is being updated to a completed status (i.e. a BTCpay has completed)
            tx0_hash, tx1_hash = msg_data['order_match_id'][:64], msg_data['order_match_id'][65:]
            #get the order_match this btcpay settles
            order_match = util.jsonrpc_api("get_order_matches",
                {'filters': [
                 {'field': 'tx0_hash', 'op': '==', 'value': tx0_hash},
                 {'field': 'tx1_hash', 'op': '==', 'value': tx1_hash}]
                }, abort_on_error=False)['result'][0]
        else:
            assert msg_data['status'] == 'completed' #should not enter a pending state for non BTC matches
            order_match = msg_data

        forward_asset_info = config.mongo_db.tracked_assets.find_one({'asset': order_match['forward_asset']})
        backward_asset_info = config.mongo_db.tracked_assets.find_one({'asset': order_match['backward_asset']})
        assert forward_asset_info and backward_asset_info
        base_asset, quote_asset = util.assets_to_asset_pair(order_match['forward_asset'], order_match['backward_asset'])
        
        #don't create trade records from order matches with BTC that are under the dust limit
        if    (order_match['forward_asset'] == config.BTC and order_match['forward_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF) \
           or (order_match['backward_asset'] == config.BTC and order_match['backward_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF):
            logger.debug("Order match %s ignored due to %s under dust limit." % (order_match['tx0_hash'] + order_match['tx1_hash'], config.BTC))
            return 'ABORT_THIS_MESSAGE_PROCESSING'

        #take divisible trade quantities to floating point
        forward_quantity = blockchain.normalize_quantity(order_match['forward_quantity'], forward_asset_info['divisible'])
        backward_quantity = blockchain.normalize_quantity(order_match['backward_quantity'], backward_asset_info['divisible'])
        
        #compose trade
        trade = {
            'block_index': config.state['cur_block']['block_index'],
            'block_time': config.state['cur_block']['block_time_obj'],
            'message_index': msg['message_index'], #secondary temporaral ordering off of when
            'order_match_id': order_match['tx0_hash'] + '_' + order_match['tx1_hash'],
            'order_match_tx0_index': order_match['tx0_index'],
            'order_match_tx1_index': order_match['tx1_index'],
            'order_match_tx0_address': order_match['tx0_address'],
            'order_match_tx1_address': order_match['tx1_address'],
            'base_asset': base_asset,
            'quote_asset': quote_asset,
            'base_quantity': order_match['forward_quantity'] if order_match['forward_asset'] == base_asset else order_match['backward_quantity'],
            'quote_quantity': order_match['backward_quantity'] if order_match['forward_asset'] == base_asset else order_match['forward_quantity'],
            'base_quantity_normalized': forward_quantity if order_match['forward_asset'] == base_asset else backward_quantity,
            'quote_quantity_normalized': backward_quantity if order_match['forward_asset'] == base_asset else forward_quantity,
        }
        trade['unit_price'] = float(
            ( D(trade['quote_quantity_normalized']) / D(trade['base_quantity_normalized']) ).quantize(
                D('.00000000'), rounding=decimal.ROUND_HALF_EVEN))
        trade['unit_price_inverse'] = float(
            ( D(trade['base_quantity_normalized']) / D(trade['quote_quantity_normalized']) ).quantize(
                D('.00000000'), rounding=decimal.ROUND_HALF_EVEN))

        config.mongo_db.trades.insert(trade)
        logger.info("Procesed Trade from tx %s :: %s" % (msg['message_index'], trade))
示例#2
0
def get_asset_info(asset, at_dt=None):
    asset_info = config.mongo_db.tracked_assets.find_one({'asset': asset})
    
    if asset not in (config.XCP, config.BTC) and at_dt and asset_info['_at_block_time'] > at_dt:
        #get the asset info at or before the given at_dt datetime
        for e in reversed(asset_info['_history']): #newest to oldest
            if e['_at_block_time'] <= at_dt:
                asset_info = e
                break
        else: #asset was created AFTER at_dt
            asset_info = None
        if asset_info is None: return None
        assert asset_info['_at_block_time'] <= at_dt
      
    #modify some of the properties of the returned asset_info for BTC and XCP
    if asset == config.BTC:
        if at_dt:
            start_block_index, end_block_index = database.get_block_indexes_for_dates(end_dt=at_dt)
            asset_info['total_issued'] = blockchain.get_btc_supply(normalize=False, at_block_index=end_block_index)
            asset_info['total_issued_normalized'] = blockchain.normalize_quantity(asset_info['total_issued'])
        else:
            asset_info['total_issued'] = blockchain.get_btc_supply(normalize=False)
            asset_info['total_issued_normalized'] = blockchain.normalize_quantity(asset_info['total_issued'])
    elif asset == config.XCP:
        #BUG: this does not take end_dt (if specified) into account. however, the deviation won't be too big
        # as XCP doesn't deflate quickly at all, and shouldn't matter that much since there weren't any/much trades
        # before the end of the burn period (which is what is involved with how we use at_dt with currently)
        asset_info['total_issued'] = util.call_jsonrpc_api("get_supply", {'asset': 'XCP'}, abort_on_error=True)['result']
        asset_info['total_issued_normalized'] = blockchain.normalize_quantity(asset_info['total_issued'])
    if not asset_info:
        raise Exception("Invalid asset: %s" % asset)
    return asset_info
示例#3
0
def get_normalized_balances(addresses):
    """
    This call augments counterparty's get_balances with a normalized_quantity field. It also will include any owned
    assets for an address, even if their balance is zero.
    NOTE: Does not retrieve BTC balance. Use get_address_info for that.
    """
    if not isinstance(addresses, list):
        raise Exception("addresses must be a list of addresses, even if it just contains one address")
    if not len(addresses):
        raise Exception("Invalid address list supplied")

    filters = []
    for address in addresses:
        filters.append({'field': 'address', 'op': '==', 'value': address})

    mappings = {}
    result = util.call_jsonrpc_api(
        "get_balances",
        {'filters': filters, 'filterop': 'or'}, abort_on_error=True)['result']

    isowner = {}
    owned_assets = config.mongo_db.tracked_assets.find(
        {'$or': [{'owner': a} for a in addresses]}, {'_history': 0, '_id': 0})
    for o in owned_assets:
        isowner[o['owner'] + o['asset']] = o

    data = []
    for d in result:
        if not d['quantity'] and ((d['address'] + d['asset']) not in isowner):
            continue  # don't include balances with a zero asset value
        asset_info = config.mongo_db.tracked_assets.find_one({'asset': d['asset']})
        divisible = True  # XCP and BTC
        if asset_info and 'divisible' in asset_info:
            divisible = asset_info['divisible']
        d['normalized_quantity'] = blockchain.normalize_quantity(d['quantity'], divisible)
        d['owner'] = (d['address'] + d['asset']) in isowner

        try:
            d['asset_longname'] = asset_info['asset_longname']
        except TypeError as e:
            d['asset_longname'] = d['asset']

        mappings[d['address'] + d['asset']] = d
        data.append(d)

    # include any owned assets for each address, even if their balance is zero
    for key in isowner:
        if key not in mappings:
            o = isowner[key]
            data.append({
                'address': o['owner'],
                'asset': o['asset'],
                'quantity': 0,
                'normalized_quantity': 0,
                'owner': True,
            })

    return data
示例#4
0
    def make_book(orders, isBidBook):
        book = {}
        for o in orders:
            if o['give_asset'] == base_asset:
                if base_asset == config.BTC and o['give_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF:
                    continue #filter dust orders, if necessary
                
                give_quantity = blockchain.normalize_quantity(o['give_quantity'], base_asset_info['divisible'])
                get_quantity = blockchain.normalize_quantity(o['get_quantity'], quote_asset_info['divisible'])
                unit_price = float(( D(get_quantity) / D(give_quantity) ))
                remaining = blockchain.normalize_quantity(o['give_remaining'], base_asset_info['divisible'])
            else:
                if quote_asset == config.BTC and o['give_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF:
                    continue #filter dust orders, if necessary

                give_quantity = blockchain.normalize_quantity(o['give_quantity'], quote_asset_info['divisible'])
                get_quantity = blockchain.normalize_quantity(o['get_quantity'], base_asset_info['divisible'])
                unit_price = float(( D(give_quantity) / D(get_quantity) ))
                remaining = blockchain.normalize_quantity(o['get_remaining'], base_asset_info['divisible'])
            id = "%s_%s_%s" % (base_asset, quote_asset, unit_price)
            #^ key = {base}_{bid}_{unit_price}, values ref entries in book
            book.setdefault(id, {'unit_price': unit_price, 'quantity': 0, 'count': 0})
            book[id]['quantity'] += remaining #base quantity outstanding
            book[id]['count'] += 1 #num orders at this price level
        book = sorted(book.itervalues(), key=operator.itemgetter('unit_price'), reverse=isBidBook)
        #^ convert to list and sort -- bid book = descending, ask book = ascending
        return book
示例#5
0
def get_normalized_balances(addresses):
    """
    This call augments counterparty's get_balances with a normalized_quantity field. It also will include any owned
    assets for an address, even if their balance is zero. 
    NOTE: Does not retrieve BTC balance. Use get_address_info for that.
    """
    if not isinstance(addresses, list):
        raise Exception("addresses must be a list of addresses, even if it just contains one address")
    if not len(addresses):
        raise Exception("Invalid address list supplied")

    filters = []
    for address in addresses:
        filters.append({'field': 'address', 'op': '==', 'value': address})

    mappings = {}
    result = util.call_jsonrpc_api(
        "get_balances",
        {'filters': filters, 'filterop': 'or'}, abort_on_error=True)['result']

    isowner = {}
    owned_assets = config.mongo_db.tracked_assets.find(
        {'$or': [{'owner': a} for a in addresses]}, {'_history': 0, '_id': 0})
    for o in owned_assets:
        isowner[o['owner'] + o['asset']] = o

    data = []
    for d in result:
        if not d['quantity'] and ((d['address'] + d['asset']) not in isowner):
            continue  # don't include balances with a zero asset value
        asset_info = config.mongo_db.tracked_assets.find_one({'asset': d['asset']})
        divisible = True  # XCP and BTC
        if asset_info and 'divisible' in asset_info:
            divisible = asset_info['divisible']
        d['normalized_quantity'] = blockchain.normalize_quantity(d['quantity'], divisible)
        d['owner'] = (d['address'] + d['asset']) in isowner
        mappings[d['address'] + d['asset']] = d
        data.append(d)

    # include any owned assets for each address, even if their balance is zero
    for key in isowner:
        if key not in mappings:
            o = isowner[key]
            data.append({
                'address': o['owner'],
                'asset': o['asset'],
                'quantity': 0,
                'normalized_quantity': 0,
                'owner': True,
            })

    return data
示例#6
0
def parse_balance_change(msg, msg_data):
    # track balance changes for each address
    bal_change = None
    if msg['category'] in ['credits', 'debits', ]:
        actionName = 'credit' if msg['category'] == 'credits' else 'debit'
        address = msg_data['address']
        asset_info = config.mongo_db.tracked_assets.find_one({'asset': msg_data['asset']})
        if asset_info is None:
            logger.warn("Credit/debit of %s where asset ('%s') does not exist. Ignoring..." % (msg_data['quantity'], msg_data['asset']))
            return 'ABORT_THIS_MESSAGE_PROCESSING'
        quantity = msg_data['quantity'] if msg['category'] == 'credits' else -msg_data['quantity']
        quantity_normalized = blockchain.normalize_quantity(quantity, asset_info['divisible'])

        # look up the previous balance to go off of
        last_bal_change = config.mongo_db.balance_changes.find_one({
            'address': address,
            'asset': asset_info['asset']
        }, sort=[("block_index", pymongo.DESCENDING), ("_id", pymongo.DESCENDING)])

        if last_bal_change \
           and last_bal_change['block_index'] == config.state['cur_block']['block_index']:
            # modify this record, as we want at most one entry per block index for each (address, asset) pair
            last_bal_change['quantity'] += quantity
            last_bal_change['quantity_normalized'] += quantity_normalized
            last_bal_change['new_balance'] += quantity
            last_bal_change['new_balance_normalized'] += quantity_normalized
            config.mongo_db.balance_changes.save(last_bal_change)
            logger.info("%s (UPDATED) %s %s %s %s (new bal: %s, msgID: %s)" % (
                actionName.capitalize(), ('%f' % last_bal_change['quantity_normalized']).rstrip('0').rstrip('.'), last_bal_change['asset'],
                'from' if actionName == 'debit' else 'to',
                last_bal_change['address'], ('%f' % last_bal_change['new_balance_normalized']).rstrip('0').rstrip('.'), msg['message_index'],))
            bal_change = last_bal_change
        else:  # new balance change record for this block
            bal_change = {
                'address': address,
                'asset': asset_info['asset'],
                'asset_longname': asset_info['asset_longname'],
                'block_index': config.state['cur_block']['block_index'],
                'block_time': config.state['cur_block']['block_time_obj'],
                'quantity': quantity,
                'quantity_normalized': quantity_normalized,
                'new_balance': last_bal_change['new_balance'] + quantity if last_bal_change else quantity,
                'new_balance_normalized': last_bal_change['new_balance_normalized'] + quantity_normalized if last_bal_change else quantity_normalized,
            }
            config.mongo_db.balance_changes.insert(bal_change)
            logger.info("%s %s %s %s %s (new bal: %s, msgID: %s)" % (
                actionName.capitalize(), ('%f' % bal_change['quantity_normalized']).rstrip('0').rstrip('.'), bal_change['asset'],
                'from' if actionName == 'debit' else 'to',
                bal_change['address'], ('%f' % bal_change['new_balance_normalized']).rstrip('0').rstrip('.'), msg['message_index'],))
示例#7
0
def parse_balance_change(msg, msg_data): 
    #track balance changes for each address
    bal_change = None
    if msg['category'] in ['credits', 'debits',]:
        actionName = 'credit' if msg['category'] == 'credits' else 'debit'
        address = msg_data['address']
        asset_info = config.mongo_db.tracked_assets.find_one({ 'asset': msg_data['asset'] })
        if asset_info is None:
            logger.warn("Credit/debit of %s where asset ('%s') does not exist. Ignoring..." % (msg_data['quantity'], msg_data['asset']))
            return 'ABORT_THIS_MESSAGE_PROCESSING'
        quantity = msg_data['quantity'] if msg['category'] == 'credits' else -msg_data['quantity']
        quantity_normalized = blockchain.normalize_quantity(quantity, asset_info['divisible'])

        #look up the previous balance to go off of
        last_bal_change = config.mongo_db.balance_changes.find_one({
            'address': address,
            'asset': asset_info['asset']
        }, sort=[("block_index", pymongo.DESCENDING), ("_id", pymongo.DESCENDING)])
        
        if last_bal_change \
           and last_bal_change['block_index'] == config.state['cur_block']['block_index']:
            #modify this record, as we want at most one entry per block index for each (address, asset) pair
            last_bal_change['quantity'] += quantity
            last_bal_change['quantity_normalized'] += quantity_normalized
            last_bal_change['new_balance'] += quantity
            last_bal_change['new_balance_normalized'] += quantity_normalized
            config.mongo_db.balance_changes.save(last_bal_change)
            logger.info("%s (UPDATED) %s %s %s %s (new bal: %s, msgID: %s)" % (
                actionName.capitalize(), ('%f' % last_bal_change['quantity_normalized']).rstrip('0').rstrip('.'), last_bal_change['asset'],
                'from' if actionName == 'debit' else 'to',
                last_bal_change['address'], ('%f' % last_bal_change['new_balance_normalized']).rstrip('0').rstrip('.'), msg['message_index'],))
            bal_change = last_bal_change
        else: #new balance change record for this block
            bal_change = {
                'address': address, 
                'asset': asset_info['asset'],
                'block_index': config.state['cur_block']['block_index'],
                'block_time': config.state['cur_block']['block_time_obj'],
                'quantity': quantity,
                'quantity_normalized': quantity_normalized,
                'new_balance': last_bal_change['new_balance'] + quantity if last_bal_change else quantity,
                'new_balance_normalized': last_bal_change['new_balance_normalized'] + quantity_normalized if last_bal_change else quantity_normalized,
            }
            config.mongo_db.balance_changes.insert(bal_change)
            logger.info("%s %s %s %s %s (new bal: %s, msgID: %s)" % (
                actionName.capitalize(), ('%f' % bal_change['quantity_normalized']).rstrip('0').rstrip('.'), bal_change['asset'],
                'from' if actionName == 'debit' else 'to',
                bal_change['address'], ('%f' % bal_change['new_balance_normalized']).rstrip('0').rstrip('.'), msg['message_index'],))
示例#8
0
def decorate_message(message, for_txn_history=False):
    # insert custom fields in certain events...
    # even invalid actions need these extra fields for proper reporting to the client (as the reporting message
    # is produced via PendingActionViewModel.calcText) -- however make it able to deal with the queried data not existing in this case
    assert '_category' in message
    if for_txn_history:
        message['_command'] = 'insert'  # history data doesn't include this
        block_index = message['block_index'] if 'block_index' in message else message['tx1_block_index']
        message['_block_time'] = database.get_block_time(block_index)
        message['_tx_index'] = message['tx_index'] if 'tx_index' in message else message.get('tx1_index', None)
        if message['_category'] in ['bet_expirations', 'order_expirations', 'bet_match_expirations', 'order_match_expirations']:
            message['_tx_index'] = 0  # add tx_index to all entries (so we can sort on it secondarily in history view), since these lack it

    if message['_category'] in ['credits', 'debits']:
        # find the last balance change on record
        bal_change = config.mongo_db.balance_changes.find_one(
            {'address': message['address'], 'asset': message['asset']},
            sort=[("block_time", pymongo.DESCENDING)])
        message['_quantity_normalized'] = abs(bal_change['quantity_normalized']) if bal_change else None
        message['_balance'] = bal_change['new_balance'] if bal_change else None
        message['_balance_normalized'] = bal_change['new_balance_normalized'] if bal_change else None

    if message['_category'] in ['orders', ] and message['_command'] == 'insert':
        get_asset_info = config.mongo_db.tracked_assets.find_one({'asset': message['get_asset']})
        give_asset_info = config.mongo_db.tracked_assets.find_one({'asset': message['give_asset']})
        message['_get_asset_divisible'] = get_asset_info['divisible'] if get_asset_info else None
        message['_give_asset_divisible'] = give_asset_info['divisible'] if give_asset_info else None

    if message['_category'] in ['order_matches', ] and message['_command'] == 'insert':
        forward_asset_info = config.mongo_db.tracked_assets.find_one({'asset': message['forward_asset']})
        backward_asset_info = config.mongo_db.tracked_assets.find_one({'asset': message['backward_asset']})
        message['_forward_asset_divisible'] = forward_asset_info['divisible'] if forward_asset_info else None
        message['_backward_asset_divisible'] = backward_asset_info['divisible'] if backward_asset_info else None

    if message['_category'] in ['orders', 'order_matches', ]:
        message['_btc_below_dust_limit'] = (
            ('forward_asset' in message and message['forward_asset'] == config.BTC and message['forward_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF)
            or ('backward_asset' in message and message['backward_asset'] == config.BTC and message['backward_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF)
        )

    if message['_category'] in ['dividends', 'sends', ]:
        asset_info = config.mongo_db.tracked_assets.find_one({'asset': message['asset']})
        message['_divisible'] = asset_info['divisible'] if asset_info else None

    if message['_category'] in ['issuances', ]:
        message['_quantity_normalized'] = blockchain.normalize_quantity(message['quantity'], message['divisible'])
    return message
示例#9
0
def parse_issuance(msg, msg_data):
    if msg['category'] != 'issuances':
        return
    if msg_data['status'] != 'valid':
        return

    cur_block_index = config.state['cur_block']['block_index']
    cur_block = config.state['cur_block']

    def modify_extended_asset_info(asset, description):
        """adds an asset to asset_extended_info collection if the description is a valid json link. or, if the link
        is not a valid json link, will remove the asset entry from the table if it exists"""
        if util.is_valid_url(description,
                             suffix='.json',
                             allow_no_protocol=True):
            config.mongo_db.asset_extended_info.update(
                {'asset': asset},
                {
                    '$set': {
                        'info_url': description,
                        'info_status': 'needfetch',
                        'fetch_info_retry':
                        0,  # retry ASSET_MAX_RETRY times to fetch info from info_url
                        'info_data': {},
                        'errors': []
                    }
                },
                upsert=True)
            #^ valid info_status settings: needfetch, valid, invalid, error
            # additional fields will be added later in events, once the asset info is pulled
        else:
            config.mongo_db.asset_extended_info.remove({'asset': asset})
            # remove any saved asset image data
            imagePath = os.path.join(config.data_dir,
                                     config.SUBDIR_ASSET_IMAGES,
                                     asset + '.png')
            if os.path.exists(imagePath):
                os.remove(imagePath)

    tracked_asset = config.mongo_db.tracked_assets.find_one(
        {'asset': msg_data['asset']}, {
            '_id': 0,
            '_history': 0
        })
    #^ pulls the tracked asset without the _id and history fields. This may be None

    if msg_data['locked']:  # lock asset
        assert tracked_asset is not None
        config.mongo_db.tracked_assets.update({'asset': msg_data['asset']}, {
            "$set": {
                '_at_block': cur_block_index,
                '_at_block_time': cur_block['block_time_obj'],
                '_change_type': 'locked',
                'locked': True,
            },
            "$push": {
                '_history': tracked_asset
            }
        },
                                              upsert=False)
        logger.info("Locking asset %s" % (msg_data['asset'], ))
    elif msg_data['transfer']:  # transfer asset
        assert tracked_asset is not None
        config.mongo_db.tracked_assets.update({'asset': msg_data['asset']}, {
            "$set": {
                '_at_block': cur_block_index,
                '_at_block_time': cur_block['block_time_obj'],
                '_change_type': 'transferred',
                'owner': msg_data['issuer'],
            },
            "$push": {
                '_history': tracked_asset
            }
        },
                                              upsert=False)
        logger.info("Transferring asset %s to address %s" %
                    (msg_data['asset'], msg_data['issuer']))
    elif msg_data[
            'quantity'] == 0 and tracked_asset is not None:  # change description
        config.mongo_db.tracked_assets.update({'asset': msg_data['asset']}, {
            "$set": {
                '_at_block': cur_block_index,
                '_at_block_time': cur_block['block_time_obj'],
                '_change_type': 'changed_description',
                'description': msg_data['description'],
            },
            "$push": {
                '_history': tracked_asset
            }
        },
                                              upsert=False)
        modify_extended_asset_info(msg_data['asset'], msg_data['description'])
        logger.info("Changing description for asset %s to '%s'" %
                    (msg_data['asset'], msg_data['description']))
    else:  # issue new asset or issue addition qty of an asset
        if not tracked_asset:  # new issuance
            tracked_asset = {
                '_change_type':
                'created',
                '_at_block':
                cur_block_index,  # the block ID this asset is current for
                '_at_block_time':
                cur_block['block_time_obj'],
                #^ NOTE: (if there are multiple asset tracked changes updates in a single block for the same
                # asset, the last one with _at_block == that block id in the history array is the
                # final version for that asset at that block
                'asset':
                msg_data['asset'],
                'owner':
                msg_data['issuer'],
                'description':
                msg_data['description'],
                'divisible':
                msg_data['divisible'],
                'locked':
                False,
                'total_issued':
                int(msg_data['quantity']),
                'total_issued_normalized':
                blockchain.normalize_quantity(msg_data['quantity'],
                                              msg_data['divisible']),
                '_history': []  # to allow for block rollbacks
            }
            config.mongo_db.tracked_assets.insert(tracked_asset)
            logger.info("Tracking new asset: %s" % msg_data['asset'])
            modify_extended_asset_info(msg_data['asset'],
                                       msg_data['description'])
        else:  # issuing additional of existing asset
            assert tracked_asset is not None
            config.mongo_db.tracked_assets.update(
                {'asset': msg_data['asset']}, {
                    "$set": {
                        '_at_block': cur_block_index,
                        '_at_block_time': cur_block['block_time_obj'],
                        '_change_type': 'issued_more',
                    },
                    "$inc": {
                        'total_issued':
                        msg_data['quantity'],
                        'total_issued_normalized':
                        blockchain.normalize_quantity(msg_data['quantity'],
                                                      msg_data['divisible'])
                    },
                    "$push": {
                        '_history': tracked_asset
                    }
                },
                upsert=False)
            logger.info("Adding additional %s quantity for asset %s" %
                        (blockchain.normalize_quantity(
                            msg_data['quantity'],
                            msg_data['divisible']), msg_data['asset']))
    return True
示例#10
0
def decorate_message(message, for_txn_history=False):
    # insert custom fields in certain events...
    # even invalid actions need these extra fields for proper reporting to the client (as the reporting message
    # is produced via PendingActionViewModel.calcText) -- however make it able to deal with the queried data not existing in this case
    assert '_category' in message
    if for_txn_history:
        message['_command'] = 'insert'  # history data doesn't include this
        block_index = message[
            'block_index'] if 'block_index' in message else message[
                'tx1_block_index']
        message['_block_time'] = database.get_block_time(block_index)
        message['_tx_index'] = message[
            'tx_index'] if 'tx_index' in message else message.get(
                'tx1_index', None)
        if message['_category'] in [
                'bet_expirations', 'order_expirations',
                'bet_match_expirations', 'order_match_expirations'
        ]:
            message[
                '_tx_index'] = 0  # add tx_index to all entries (so we can sort on it secondarily in history view), since these lack it

    if message['_category'] in ['credits', 'debits']:
        # find the last balance change on record
        bal_change = config.mongo_db.balance_changes.find_one(
            {
                'address': message['address'],
                'asset': message['asset']
            },
            sort=[("block_time", pymongo.DESCENDING)])
        message['_quantity_normalized'] = abs(
            bal_change['quantity_normalized']) if bal_change else None
        message['_balance'] = bal_change['new_balance'] if bal_change else None
        message['_balance_normalized'] = bal_change[
            'new_balance_normalized'] if bal_change else None

    if message['_category'] in [
            'orders',
    ] and message['_command'] == 'insert':
        get_asset_info = config.mongo_db.tracked_assets.find_one(
            {'asset': message['get_asset']})
        give_asset_info = config.mongo_db.tracked_assets.find_one(
            {'asset': message['give_asset']})
        message['_get_asset_divisible'] = get_asset_info[
            'divisible'] if get_asset_info else None
        message['_give_asset_divisible'] = give_asset_info[
            'divisible'] if give_asset_info else None

    if message['_category'] in [
            'order_matches',
    ] and message['_command'] == 'insert':
        forward_asset_info = config.mongo_db.tracked_assets.find_one(
            {'asset': message['forward_asset']})
        backward_asset_info = config.mongo_db.tracked_assets.find_one(
            {'asset': message['backward_asset']})
        message['_forward_asset_divisible'] = forward_asset_info[
            'divisible'] if forward_asset_info else None
        message['_backward_asset_divisible'] = backward_asset_info[
            'divisible'] if backward_asset_info else None

    if message['_category'] in [
            'orders',
            'order_matches',
    ]:
        message['_btc_below_dust_limit'] = (
            ('forward_asset' in message
             and message['forward_asset'] == config.BTC and
             message['forward_quantity'] <= config.ORDER_BTC_DUST_LIMIT_CUTOFF)
            or ('backward_asset' in message
                and message['backward_asset'] == config.BTC
                and message['backward_quantity'] <=
                config.ORDER_BTC_DUST_LIMIT_CUTOFF))

    if message['_category'] in [
            'dividends',
            'sends',
    ]:
        asset_info = config.mongo_db.tracked_assets.find_one(
            {'asset': message['asset']})
        message['_divisible'] = asset_info['divisible'] if asset_info else None

    if message['_category'] in [
            'issuances',
    ]:
        message['_quantity_normalized'] = blockchain.normalize_quantity(
            message['quantity'], message['divisible'])
    return message
def compile_asset_pair_market_info():
    """Compiles the pair-level statistics that show on the View Prices page of counterwallet, for instance"""
    #loop through all open orders, and compile a listing of pairs, with a count of open orders for each pair
    mongo_db = config.mongo_db
    end_dt = datetime.datetime.utcnow()
    start_dt = end_dt - datetime.timedelta(days=1)
    start_block_index, end_block_index = database.get_block_indexes_for_dates(
        start_dt=start_dt, end_dt=end_dt)
    open_orders = util.call_jsonrpc_api("get_orders", {
        'filters': [
            {
                'field': 'give_remaining',
                'op': '>',
                'value': 0
            },
            {
                'field': 'get_remaining',
                'op': '>',
                'value': 0
            },
            {
                'field': 'fee_required_remaining',
                'op': '>=',
                'value': 0
            },
            {
                'field': 'fee_provided_remaining',
                'op': '>=',
                'value': 0
            },
        ],
        'status':
        'open',
        'show_expired':
        False,
    },
                                        abort_on_error=True)['result']
    pair_data = {}
    asset_info = {}

    def get_price(base_quantity_normalized, quote_quantity_normalized):
        return float(D(quote_quantity_normalized / base_quantity_normalized))

    #COMPOSE order depth, lowest ask, and highest bid column data
    for o in open_orders:
        (base_asset,
         quote_asset) = util.assets_to_asset_pair(o['give_asset'],
                                                  o['get_asset'])
        pair = '%s/%s' % (base_asset, quote_asset)
        base_asset_info = asset_info.get(
            base_asset, mongo_db.tracked_assets.find_one({'asset':
                                                          base_asset}))
        if base_asset not in asset_info:
            asset_info[base_asset] = base_asset_info
        quote_asset_info = asset_info.get(
            quote_asset,
            mongo_db.tracked_assets.find_one({'asset': quote_asset}))
        if quote_asset not in asset_info:
            asset_info[quote_asset] = quote_asset_info

        pair_data.setdefault(
            pair, {
                'open_orders_count': 0,
                'lowest_ask': None,
                'highest_bid': None,
                'completed_trades_count': 0,
                'vol_base': 0,
                'vol_quote': 0
            })
        #^ highest ask = open order selling base, highest bid = open order buying base
        #^ we also initialize completed_trades_count, vol_base, vol_quote because every pair inited here may
        # not have cooresponding data out of the trades_data_by_pair aggregation below
        pair_data[pair]['open_orders_count'] += 1
        base_quantity_normalized = blockchain.normalize_quantity(
            o['give_quantity'] if base_asset == o['give_asset'] else
            o['get_quantity'], base_asset_info['divisible'])
        quote_quantity_normalized = blockchain.normalize_quantity(
            o['give_quantity'] if quote_asset == o['give_asset'] else
            o['get_quantity'], quote_asset_info['divisible'])
        order_price = get_price(base_quantity_normalized,
                                quote_quantity_normalized)
        if base_asset == o['give_asset']:  #selling base
            if pair_data[pair]['lowest_ask'] is None or order_price < pair_data[
                    pair]['lowest_ask']:
                pair_data[pair]['lowest_ask'] = order_price
        elif base_asset == o['get_asset']:  #buying base
            if pair_data[pair][
                    'highest_bid'] is None or order_price > pair_data[pair][
                        'highest_bid']:
                pair_data[pair]['highest_bid'] = order_price

    #COMPOSE volume data (in XCP and BTC), and % change data
    #loop through all trade volume over the past 24h, and match that to the open orders
    trades_data_by_pair = mongo_db.trades.aggregate([
        {
            "$match": {
                "block_time": {
                    "$gte": start_dt,
                    "$lte": end_dt
                }
            }
        },
        {
            "$project": {
                "base_asset": 1,
                "quote_asset": 1,
                "base_quantity_normalized": 1,  #to derive base volume
                "quote_quantity_normalized": 1  #to derive quote volume
            }
        },
        {
            "$group": {
                "_id": {
                    "base_asset": "$base_asset",
                    "quote_asset": "$quote_asset"
                },
                "vol_base": {
                    "$sum": "$base_quantity_normalized"
                },
                "vol_quote": {
                    "$sum": "$quote_quantity_normalized"
                },
                "count": {
                    "$sum": 1
                },
            }
        }
    ])
    trades_data_by_pair = [] if not trades_data_by_pair[
        'ok'] else trades_data_by_pair['result']
    for e in trades_data_by_pair:
        pair = '%s/%s' % (e['_id']['base_asset'], e['_id']['quote_asset'])
        pair_data.setdefault(pair, {
            'open_orders_count': 0,
            'lowest_ask': None,
            'highest_bid': None
        })
        #^ initialize an empty pair in the event there are no open orders for that pair, but there ARE completed trades for it
        pair_data[pair]['completed_trades_count'] = e['count']
        pair_data[pair]['vol_base'] = e['vol_base']
        pair_data[pair]['vol_quote'] = e['vol_quote']

    #compose price data, relative to BTC and XCP
    mps_xcp_btc, xcp_btc_price, btc_xcp_price = get_price_primatives()
    for pair, e in pair_data.iteritems():
        base_asset, quote_asset = pair.split('/')
        _24h_vol_in_btc = None
        _24h_vol_in_xcp = None
        #derive asset price data, expressed in BTC and XCP, for the given volumes
        if base_asset == config.XCP:
            _24h_vol_in_xcp = e['vol_base']
            _24h_vol_in_btc = blockchain.round_out(
                e['vol_base'] * xcp_btc_price) if xcp_btc_price else 0
        elif base_asset == config.BTC:
            _24h_vol_in_xcp = blockchain.round_out(
                e['vol_base'] * btc_xcp_price) if btc_xcp_price else 0
            _24h_vol_in_btc = e['vol_base']
        else:  #base is not XCP or BTC
            price_summary_in_xcp, price_summary_in_btc, price_in_xcp, price_in_btc, aggregated_price_in_xcp, aggregated_price_in_btc = \
                get_xcp_btc_price_info(base_asset, mps_xcp_btc, xcp_btc_price, btc_xcp_price, with_last_trades=0, start_dt=start_dt, end_dt=end_dt)
            if price_in_xcp:
                _24h_vol_in_xcp = blockchain.round_out(e['vol_base'] *
                                                       price_in_xcp)
            if price_in_btc:
                _24h_vol_in_btc = blockchain.round_out(e['vol_base'] *
                                                       price_in_btc)

            if _24h_vol_in_xcp is None or _24h_vol_in_btc is None:
                #the base asset didn't have price data against BTC or XCP, or both...try against the quote asset instead
                price_summary_in_xcp, price_summary_in_btc, price_in_xcp, price_in_btc, aggregated_price_in_xcp, aggregated_price_in_btc = \
                    get_xcp_btc_price_info(quote_asset, mps_xcp_btc, xcp_btc_price, btc_xcp_price, with_last_trades=0, start_dt=start_dt, end_dt=end_dt)
                if _24h_vol_in_xcp is None and price_in_xcp:
                    _24h_vol_in_xcp = blockchain.round_out(e['vol_quote'] *
                                                           price_in_xcp)
                if _24h_vol_in_btc is None and price_in_btc:
                    _24h_vol_in_btc = blockchain.round_out(e['vol_quote'] *
                                                           price_in_btc)
            pair_data[pair]['24h_vol_in_{}'.format(
                config.XCP.lower())] = _24h_vol_in_xcp  #might still be None
            pair_data[pair]['24h_vol_in_{}'.format(
                config.BTC.lower())] = _24h_vol_in_btc  #might still be None

        #get % change stats -- start by getting the first trade directly before the 24h period starts
        prev_trade = mongo_db.trades.find({
            "base_asset": base_asset,
            "quote_asset": quote_asset,
            "block_time": {
                '$lt': start_dt
            }
        }).sort('block_time', pymongo.DESCENDING).limit(1)
        latest_trade = mongo_db.trades.find({
            "base_asset": base_asset,
            "quote_asset": quote_asset
        }).sort('block_time', pymongo.DESCENDING).limit(1)
        if not prev_trade.count():  #no previous trade before this 24hr period
            pair_data[pair]['24h_pct_change'] = None
        else:
            prev_trade = prev_trade[0]
            latest_trade = latest_trade[0]
            prev_trade_price = get_price(
                prev_trade['base_quantity_normalized'],
                prev_trade['quote_quantity_normalized'])
            latest_trade_price = get_price(
                latest_trade['base_quantity_normalized'],
                latest_trade['quote_quantity_normalized'])
            pair_data[pair]['24h_pct_change'] = (
                (latest_trade_price - prev_trade_price) /
                prev_trade_price) * 100
        pair_data[pair]['last_updated'] = end_dt
        #print "PRODUCED", pair, pair_data[pair]
        mongo_db.asset_pair_market_info.update(
            {
                'base_asset': base_asset,
                'quote_asset': quote_asset
            }, {"$set": pair_data[pair]},
            upsert=True)

    #remove any old pairs that were not just updated
    mongo_db.asset_pair_market_info.remove({'last_updated': {'$lt': end_dt}})
    logger.info("Recomposed 24h trade statistics for %i asset pairs: %s" %
                (len(pair_data), ', '.join(pair_data.keys())))
示例#12
0
def compile_asset_pair_market_info():
    """Compiles the pair-level statistics that show on the View Prices page of counterwallet, for instance"""
    #loop through all open orders, and compile a listing of pairs, with a count of open orders for each pair
    end_dt = datetime.datetime.utcnow()
    start_dt = end_dt - datetime.timedelta(days=1)
    start_block_index, end_block_index = database.get_block_indexes_for_dates(start_dt=start_dt, end_dt=end_dt)
    open_orders = util.call_jsonrpc_api("get_orders",
        { 'filters': [
            {'field': 'give_remaining', 'op': '>', 'value': 0},
            {'field': 'get_remaining', 'op': '>', 'value': 0},
            {'field': 'fee_required_remaining', 'op': '>=', 'value': 0},
            {'field': 'fee_provided_remaining', 'op': '>=', 'value': 0},
          ],
          'status': 'open',
          'show_expired': False,
        }, abort_on_error=True)['result']
    pair_data = {}
    asset_info = {}
    
    def get_price(base_quantity_normalized, quote_quantity_normalized):
        return float(D(quote_quantity_normalized / base_quantity_normalized ))
    
    #COMPOSE order depth, lowest ask, and highest bid column data
    for o in open_orders:
        (base_asset, quote_asset) = util.assets_to_asset_pair(o['give_asset'], o['get_asset'])
        pair = '%s/%s' % (base_asset, quote_asset)
        base_asset_info = asset_info.get(base_asset, config.mongo_db.tracked_assets.find_one({ 'asset': base_asset }))
        if base_asset not in asset_info: asset_info[base_asset] = base_asset_info
        quote_asset_info = asset_info.get(quote_asset, config.mongo_db.tracked_assets.find_one({ 'asset': quote_asset }))
        if quote_asset not in asset_info: asset_info[quote_asset] = quote_asset_info
        
        pair_data.setdefault(pair, {'open_orders_count': 0, 'lowest_ask': None, 'highest_bid': None,
            'completed_trades_count': 0, 'vol_base': 0, 'vol_quote': 0})
        #^ highest ask = open order selling base, highest bid = open order buying base
        #^ we also initialize completed_trades_count, vol_base, vol_quote because every pair inited here may
        # not have cooresponding data out of the trades_data_by_pair aggregation below
        pair_data[pair]['open_orders_count'] += 1
        base_quantity_normalized = blockchain.normalize_quantity(o['give_quantity'] if base_asset == o['give_asset'] else o['get_quantity'], base_asset_info['divisible'])
        quote_quantity_normalized = blockchain.normalize_quantity(o['give_quantity'] if quote_asset == o['give_asset'] else o['get_quantity'], quote_asset_info['divisible'])
        order_price = get_price(base_quantity_normalized, quote_quantity_normalized)
        if base_asset == o['give_asset']: #selling base
            if pair_data[pair]['lowest_ask'] is None or order_price < pair_data[pair]['lowest_ask']: 
                pair_data[pair]['lowest_ask'] = order_price
        elif base_asset == o['get_asset']: #buying base
            if pair_data[pair]['highest_bid'] is None or order_price > pair_data[pair]['highest_bid']:
                pair_data[pair]['highest_bid'] = order_price
    
    #COMPOSE volume data (in XCP and BTC), and % change data
    #loop through all trade volume over the past 24h, and match that to the open orders
    trades_data_by_pair = config.mongo_db.trades.aggregate([
        {"$match": {
            "block_time": {"$gte": start_dt, "$lte": end_dt } }
        },
        {"$project": {
            "base_asset": 1,
            "quote_asset": 1,
            "base_quantity_normalized": 1, #to derive base volume
            "quote_quantity_normalized": 1 #to derive quote volume
        }},
        {"$group": {
            "_id":   {"base_asset": "$base_asset", "quote_asset": "$quote_asset"},
            "vol_base":   {"$sum": "$base_quantity_normalized"},
            "vol_quote":   {"$sum": "$quote_quantity_normalized"},
            "count": {"$sum": 1},
        }}
    ])
    for e in trades_data_by_pair:
        pair = '%s/%s' % (e['_id']['base_asset'], e['_id']['quote_asset'])
        pair_data.setdefault(pair, {'open_orders_count': 0, 'lowest_ask': None, 'highest_bid': None})
        #^ initialize an empty pair in the event there are no open orders for that pair, but there ARE completed trades for it
        pair_data[pair]['completed_trades_count'] = e['count']
        pair_data[pair]['vol_base'] = e['vol_base'] 
        pair_data[pair]['vol_quote'] = e['vol_quote'] 
    
    #compose price data, relative to BTC and XCP
    mps_xcp_btc, xcp_btc_price, btc_xcp_price = get_price_primatives()
    for pair, e in pair_data.iteritems():
        base_asset, quote_asset = pair.split('/')
        _24h_vol_in_btc = None
        _24h_vol_in_xcp = None
        #derive asset price data, expressed in BTC and XCP, for the given volumes
        if base_asset == config.XCP:
            _24h_vol_in_xcp = e['vol_base']
            _24h_vol_in_btc = blockchain.round_out(e['vol_base'] * xcp_btc_price) if xcp_btc_price else 0
        elif base_asset == config.BTC:
            _24h_vol_in_xcp = blockchain.round_out(e['vol_base'] * btc_xcp_price) if btc_xcp_price else 0
            _24h_vol_in_btc = e['vol_base']
        else: #base is not XCP or BTC
            price_summary_in_xcp, price_summary_in_btc, price_in_xcp, price_in_btc, aggregated_price_in_xcp, aggregated_price_in_btc = \
                get_xcp_btc_price_info(base_asset, mps_xcp_btc, xcp_btc_price, btc_xcp_price, with_last_trades=0, start_dt=start_dt, end_dt=end_dt)
            if price_in_xcp:
                _24h_vol_in_xcp = blockchain.round_out(e['vol_base'] * price_in_xcp)
            if price_in_btc:
                _24h_vol_in_btc = blockchain.round_out(e['vol_base'] * price_in_btc)
            
            if _24h_vol_in_xcp is None or _24h_vol_in_btc is None:
                #the base asset didn't have price data against BTC or XCP, or both...try against the quote asset instead
                price_summary_in_xcp, price_summary_in_btc, price_in_xcp, price_in_btc, aggregated_price_in_xcp, aggregated_price_in_btc = \
                    get_xcp_btc_price_info(quote_asset, mps_xcp_btc, xcp_btc_price, btc_xcp_price, with_last_trades=0, start_dt=start_dt, end_dt=end_dt)
                if _24h_vol_in_xcp is None and price_in_xcp:
                    _24h_vol_in_xcp = blockchain.round_out(e['vol_quote'] * price_in_xcp)
                if _24h_vol_in_btc is None and price_in_btc:
                    _24h_vol_in_btc = blockchain.round_out(e['vol_quote'] * price_in_btc)
            pair_data[pair]['24h_vol_in_{}'.format(config.XCP.lower())] = _24h_vol_in_xcp #might still be None
            pair_data[pair]['24h_vol_in_{}'.format(config.BTC.lower())] = _24h_vol_in_btc #might still be None
        
        #get % change stats -- start by getting the first trade directly before the 24h period starts
        prev_trade = config.mongo_db.trades.find({
            "base_asset": base_asset,
            "quote_asset": quote_asset,
            "block_time": {'$lt': start_dt}}).sort('block_time', pymongo.DESCENDING).limit(1)
        latest_trade = config.mongo_db.trades.find({
            "base_asset": base_asset,
            "quote_asset": quote_asset}).sort('block_time', pymongo.DESCENDING).limit(1)
        if not prev_trade.count(): #no previous trade before this 24hr period
            pair_data[pair]['24h_pct_change'] = None
        else:
            prev_trade = prev_trade[0]
            latest_trade = latest_trade[0]
            prev_trade_price = get_price(prev_trade['base_quantity_normalized'], prev_trade['quote_quantity_normalized'])
            latest_trade_price = get_price(latest_trade['base_quantity_normalized'], latest_trade['quote_quantity_normalized'])
            pair_data[pair]['24h_pct_change'] = ((latest_trade_price - prev_trade_price) / prev_trade_price) * 100
        pair_data[pair]['last_updated'] = end_dt
        #print "PRODUCED", pair, pair_data[pair] 
        config.mongo_db.asset_pair_market_info.update( {'base_asset': base_asset, 'quote_asset': quote_asset}, {"$set": pair_data[pair]}, upsert=True)
        
    #remove any old pairs that were not just updated
    config.mongo_db.asset_pair_market_info.remove({'last_updated': {'$lt': end_dt}})
    logger.info("Recomposed 24h trade statistics for %i asset pairs: %s" % (len(pair_data), ', '.join(pair_data.keys())))
示例#13
0
def parse_issuance(msg, msg_data):
    if msg['category'] != 'issuances': return
    if msg_data['status'] != 'valid': return
    
    cur_block_index = config.state['cur_block']['block_index']
    cur_block = config.state['cur_block']

    def modify_extended_asset_info(asset, description):
        """adds an asset to asset_extended_info collection if the description is a valid json link. or, if the link
        is not a valid json link, will remove the asset entry from the table if it exists"""
        if util.is_valid_url(description, suffix='.json', allow_no_protocol=True):
            config.mongo_db.asset_extended_info.update({'asset': asset},
                {'$set': {
                    'info_url': description,
                    'info_status': 'needfetch',
                    'fetch_info_retry': 0, # retry ASSET_MAX_RETRY times to fetch info from info_url
                    'info_data': {},
                    'errors': []
                }}, upsert=True)
            #^ valid info_status settings: needfetch, valid, invalid, error
            #additional fields will be added later in events, once the asset info is pulled
        else:
            config.mongo_db.asset_extended_info.remove({ 'asset': asset })
            #remove any saved asset image data
            imagePath = os.path.join(config.data_dir, config.SUBDIR_ASSET_IMAGES, asset + '.png')
            if os.path.exists(imagePath):
                os.remove(imagePath)

    tracked_asset = config.mongo_db.tracked_assets.find_one(
        {'asset': msg_data['asset']}, {'_id': 0, '_history': 0})
    #^ pulls the tracked asset without the _id and history fields. This may be None
    
    if msg_data['locked']: #lock asset
        assert tracked_asset is not None
        config.mongo_db.tracked_assets.update(
            {'asset': msg_data['asset']},
            {"$set": {
                '_at_block': cur_block_index,
                '_at_block_time': cur_block['block_time_obj'], 
                '_change_type': 'locked',
                'locked': True,
             },
             "$push": {'_history': tracked_asset } }, upsert=False)
        logger.info("Locking asset %s" % (msg_data['asset'],))
    elif msg_data['transfer']: #transfer asset
        assert tracked_asset is not None
        config.mongo_db.tracked_assets.update(
            {'asset': msg_data['asset']},
            {"$set": {
                '_at_block': cur_block_index,
                '_at_block_time': cur_block['block_time_obj'], 
                '_change_type': 'transferred',
                'owner': msg_data['issuer'],
             },
             "$push": {'_history': tracked_asset } }, upsert=False)
        logger.info("Transferring asset %s to address %s" % (msg_data['asset'], msg_data['issuer']))
    elif msg_data['quantity'] == 0 and tracked_asset is not None: #change description
        config.mongo_db.tracked_assets.update(
            {'asset': msg_data['asset']},
            {"$set": {
                '_at_block': cur_block_index,
                '_at_block_time': cur_block['block_time_obj'], 
                '_change_type': 'changed_description',
                'description': msg_data['description'],
             },
             "$push": {'_history': tracked_asset } }, upsert=False)
        modify_extended_asset_info(msg_data['asset'], msg_data['description'])
        logger.info("Changing description for asset %s to '%s'" % (msg_data['asset'], msg_data['description']))
    else: #issue new asset or issue addition qty of an asset
        if not tracked_asset: #new issuance
            tracked_asset = {
                '_change_type': 'created',
                '_at_block': cur_block_index, #the block ID this asset is current for
                '_at_block_time': cur_block['block_time_obj'], 
                #^ NOTE: (if there are multiple asset tracked changes updates in a single block for the same
                # asset, the last one with _at_block == that block id in the history array is the
                # final version for that asset at that block
                'asset': msg_data['asset'],
                'owner': msg_data['issuer'],
                'description': msg_data['description'],
                'divisible': msg_data['divisible'],
                'locked': False,
                'total_issued': int(msg_data['quantity']),
                'total_issued_normalized': blockchain.normalize_quantity(msg_data['quantity'], msg_data['divisible']),
                '_history': [] #to allow for block rollbacks
            }
            config.mongo_db.tracked_assets.insert(tracked_asset)
            logger.info("Tracking new asset: %s" % msg_data['asset'])
            modify_extended_asset_info(msg_data['asset'], msg_data['description'])
        else: #issuing additional of existing asset
            assert tracked_asset is not None
            config.mongo_db.tracked_assets.update(
                {'asset': msg_data['asset']},
                {"$set": {
                    '_at_block': cur_block_index,
                    '_at_block_time': cur_block['block_time_obj'], 
                    '_change_type': 'issued_more',
                 },
                 "$inc": {
                     'total_issued': msg_data['quantity'],
                     'total_issued_normalized': blockchain.normalize_quantity(msg_data['quantity'], msg_data['divisible'])
                 },
                 "$push": {'_history': tracked_asset} }, upsert=False)
            logger.info("Adding additional %s quantity for asset %s" % (
                blockchain.normalize_quantity(msg_data['quantity'], msg_data['divisible']), msg_data['asset']))
    return True