def parse (db, tx, message): dividend_parse_cursor = db.cursor() # Unpack message. try: if (tx['block_index'] > 288150 or config.TESTNET) and len(message) == LENGTH_2: quantity_per_unit, asset_id, dividend_asset_id = struct.unpack(FORMAT_2, message) asset = util.get_asset_name(db, asset_id, tx['block_index']) dividend_asset = util.get_asset_name(db, dividend_asset_id, tx['block_index']) status = 'valid' elif len(message) == LENGTH_1: quantity_per_unit, asset_id = struct.unpack(FORMAT_1, message) asset = util.get_asset_name(db, asset_id, tx['block_index']) dividend_asset = config.XCP status = 'valid' else: raise exceptions.UnpackError except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: dividend_asset, quantity_per_unit, asset = None, None, None status = 'invalid: could not unpack' if dividend_asset == config.BTC: status = 'invalid: cannot pay {} dividends within protocol'.format(config.BTC) if status == 'valid': # For SQLite3 quantity_per_unit = min(quantity_per_unit, config.MAX_INT) dividend_total, outputs, problems, fee = validate(db, tx['source'], quantity_per_unit, asset, dividend_asset, block_index=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': # Debit. util.debit(db, tx['source'], dividend_asset, dividend_total, action='dividend', event=tx['tx_hash']) if tx['block_index'] >= 330000 or config.TESTNET: # Protocol change. util.debit(db, tx['source'], config.XCP, fee, action='dividend fee', event=tx['tx_hash']) # Credit. for output in outputs: util.credit(db, output['address'], dividend_asset, output['dividend_quantity'], action='dividend', event=tx['tx_hash']) # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'dividend_asset': dividend_asset, 'quantity_per_unit': quantity_per_unit, 'fee_paid': fee, 'status': status, } sql='insert into dividends values(:tx_index, :tx_hash, :block_index, :source, :asset, :dividend_asset, :quantity_per_unit, :fee_paid, :status)' dividend_parse_cursor.execute(sql, bindings) dividend_parse_cursor.close()
def unpack(db, message): try: asset_id, quantity, tag = struct.unpack(FORMAT, message) asset = util.get_asset_name(db, asset_id, util.CURRENT_BLOCK_INDEX) except struct.error: raise UnpackError('could not unpack') except AssetIDError: raise UnpackError('asset id invalid') return asset, quantity, tag
def unpack(db, message, block_index): try: asset_id, quantity = struct.unpack(FORMAT, message) asset = util.get_asset_name(db, asset_id, block_index) except struct.error: raise UnpackError('could not unpack') except AssetNameError: raise UnpackError('asset id invalid') unpacked = {'asset': asset, 'quantity': quantity} return unpacked
def unpack(db, message, block_index): try: asset_id, quantity = struct.unpack(FORMAT, message) asset = util.get_asset_name(db, asset_id, block_index) except struct.error: raise UnpackError('could not unpack') except AssetNameError: raise UnpackError('asset id invalid') unpacked = { 'asset': asset, 'quantity': quantity } return unpacked
def parse (db, tx, message): order_parse_cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError give_id, give_quantity, get_id, get_quantity, expiration, fee_required = struct.unpack(FORMAT, message) give_asset = util.get_asset_name(db, give_id, tx['block_index']) get_asset = util.get_asset_name(db, get_id, tx['block_index']) status = 'open' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required = 0, 0, 0, 0, 0, 0 status = 'invalid: could not unpack' price = 0 if status == 'open': try: price = util.price(get_quantity, give_quantity) except ZeroDivisionError: price = 0 # Overorder order_parse_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], give_asset)) balances = list(order_parse_cursor) if give_asset != config.BTC: if not balances: give_quantity = 0 else: balance = balances[0]['quantity'] if balance < give_quantity: give_quantity = balance get_quantity = int(price * give_quantity) problems = validate(db, tx['source'], give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) # Debit give quantity. (Escrow.) if status == 'open': if give_asset != config.BTC: # No need (or way) to debit BTC. util.debit(db, tx['source'], give_asset, give_quantity, action='open order', event=tx['tx_hash']) # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'give_asset': give_asset, 'give_quantity': give_quantity, 'give_remaining': give_quantity, 'get_asset': get_asset, 'get_quantity': get_quantity, 'get_remaining': get_quantity, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'fee_required': fee_required, 'fee_required_remaining': fee_required, 'fee_provided': tx['fee'], 'fee_provided_remaining': tx['fee'], 'status': status, } sql='insert into orders values(:tx_index, :tx_hash, :block_index, :source, :give_asset, :give_quantity, :give_remaining, :get_asset, :get_quantity, :get_remaining, :expiration, :expire_index, :fee_required, :fee_required_remaining, :fee_provided, :fee_provided_remaining, :status)' order_parse_cursor.execute(sql, bindings) # Match. if status == 'open' and tx['block_index'] != config.MEMPOOL_BLOCK_INDEX: match(db, tx) order_parse_cursor.close()
def parse(db, tx, message): order_parse_cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError give_id, give_quantity, get_id, get_quantity, expiration, fee_required = struct.unpack( FORMAT, message) give_asset = util.get_asset_name(db, give_id, tx['block_index']) get_asset = util.get_asset_name(db, get_id, tx['block_index']) status = 'open' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required = 0, 0, 0, 0, 0, 0 status = 'invalid: could not unpack' price = 0 if status == 'open': try: price = util.price(get_quantity, give_quantity) except ZeroDivisionError: price = 0 # Overorder order_parse_cursor.execute( '''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], give_asset)) balances = list(order_parse_cursor) if give_asset != config.BTC: if not balances: give_quantity = 0 else: balance = balances[0]['quantity'] if balance < give_quantity: give_quantity = balance get_quantity = int(price * give_quantity) problems = validate(db, tx['source'], give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if util.enabled('btc_order_minimum'): min_btc_quantity = 0.001 * config.UNIT # 0.001 BTC if (give_asset == config.BTC and give_quantity < min_btc_quantity ) or (get_asset == config.BTC and get_quantity < min_btc_quantity): if problems: status += '; btc order below minimum' else: status = 'invalid: btc order below minimum' # Debit give quantity. (Escrow.) if status == 'open': if give_asset != config.BTC: # No need (or way) to debit BTC. util.debit(db, tx['source'], give_asset, give_quantity, action='open order', event=tx['tx_hash']) # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'give_asset': give_asset, 'give_quantity': give_quantity, 'give_remaining': give_quantity, 'get_asset': get_asset, 'get_quantity': get_quantity, 'get_remaining': get_quantity, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'fee_required': fee_required, 'fee_required_remaining': fee_required, 'fee_provided': tx['fee'], 'fee_provided_remaining': tx['fee'], 'status': status, } if "integer overflow" not in status: sql = 'insert into orders values(:tx_index, :tx_hash, :block_index, :source, :give_asset, :give_quantity, :give_remaining, :get_asset, :get_quantity, :get_remaining, :expiration, :expire_index, :fee_required, :fee_required_remaining, :fee_provided, :fee_provided_remaining, :status)' order_parse_cursor.execute(sql, bindings) else: logger.warn("Not storing [order] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # Match. if status == 'open' and tx['block_index'] != config.MEMPOOL_BLOCK_INDEX: match(db, tx) order_parse_cursor.close()
def parse (db, tx, message): dividend_parse_cursor = db.cursor() # Unpack message. try: if (tx['block_index'] > 288150 or config.TESTNET or config.REGTEST) and len(message) == LENGTH_2: quantity_per_unit, asset_id, dividend_asset_id = struct.unpack(FORMAT_2, message) asset = util.get_asset_name(db, asset_id, tx['block_index']) dividend_asset = util.get_asset_name(db, dividend_asset_id, tx['block_index']) status = 'valid' elif len(message) == LENGTH_1: quantity_per_unit, asset_id = struct.unpack(FORMAT_1, message) asset = util.get_asset_name(db, asset_id, tx['block_index']) dividend_asset = config.XCP status = 'valid' else: raise exceptions.UnpackError except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: dividend_asset, quantity_per_unit, asset = None, None, None status = 'invalid: could not unpack' if dividend_asset == config.BTC: status = 'invalid: cannot pay {} dividends within protocol'.format(config.BTC) if status == 'valid': # For SQLite3 quantity_per_unit = min(quantity_per_unit, config.MAX_INT) dividend_total, outputs, problems, fee = validate(db, tx['source'], quantity_per_unit, asset, dividend_asset, block_index=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': # Debit. util.debit(db, tx['source'], dividend_asset, dividend_total, action='dividend', event=tx['tx_hash']) if tx['block_index'] >= 330000 or config.TESTNET or config.REGTEST: # Protocol change. util.debit(db, tx['source'], config.XCP, fee, action='dividend fee', event=tx['tx_hash']) # Credit. for output in outputs: if not util.enabled('dont_credit_zero_dividend') or output['dividend_quantity'] > 0: util.credit(db, output['address'], dividend_asset, output['dividend_quantity'], action='dividend', event=tx['tx_hash']) # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'dividend_asset': dividend_asset, 'quantity_per_unit': quantity_per_unit, 'fee_paid': fee, 'status': status, } if "integer overflow" not in status: sql = 'insert into dividends values(:tx_index, :tx_hash, :block_index, :source, :asset, :dividend_asset, :quantity_per_unit, :fee_paid, :status)' dividend_parse_cursor.execute(sql, bindings) else: logger.warn("Not storing [dividend] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) dividend_parse_cursor.close()
def parse(db, tx, message): try: unpacked = unpack(db, message, tx['block_index']) status = 'valid' except (struct.error) as e: status = 'invalid: truncated message' except (exceptions.AssetNameError, exceptions.AssetIDError) as e: status = 'invalid: invalid asset name/id' except (Exception) as e: status = 'invalid: couldn\'t unpack; %s' % e cursor = db.cursor() plain_sends = [] all_debits = [] all_credits = [] if status == 'valid': for asset_id in unpacked: try: asset = util.get_asset_name(db, asset_id, tx['block_index']) except (exceptions.AssetNameError) as e: status = 'invalid: asset %s invalid at block index %i' % ( asset_id, tx['block_index']) break cursor.execute( '''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], asset_id)) balances = cursor.fetchall() if not balances: status = 'invalid: insufficient funds for asset %s, address %s has no balance' % ( asset_id, tx['source']) break credits = unpacked[asset_id] total_sent = reduce(lambda p, t: p + t[1], credits, 0) if balances[0]['quantity'] < total_sent: status = 'invalid: insufficient funds for asset %s, needs %i' % ( asset_id, total_sent) break if status == 'valid': plain_sends += map(lambda t: util.py34TupleAppend(asset_id, t), credits) all_credits += map( lambda t: { "asset": asset_id, "destination": t[0], "quantity": t[1] }, credits) all_debits.append({"asset": asset_id, "quantity": total_sent}) if status == 'valid': problems = validate(db, tx['source'], plain_sends, tx['block_index']) if problems: status = 'invalid:' + '; '.join(problems) if status == 'valid': for op in all_credits: util.credit(db, op['destination'], op['asset'], op['quantity'], action='mpma send', event=tx['tx_hash']) for op in all_debits: util.debit(db, tx['source'], op['asset'], op['quantity'], action='mpma send', event=tx['tx_hash']) # Enumeration of the plain sends needs to be deterministic, so we sort them by asset and then by address plain_sends = sorted(plain_sends, key=lambda x: ''.join([x[0], x[1]])) for i, op in enumerate(plain_sends): if len(op) > 3: memo_bytes = op[3] else: memo_bytes = None bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': op[0], 'destination': op[1], 'quantity': op[2], 'status': status, 'memo': memo_bytes, 'msg_index': i } sql = 'insert into sends (tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo, msg_index) values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo, :msg_index)' cursor.execute(sql, bindings) if status != 'valid': logger.warn("Not storing [mpma] tx [%s]: %s" % (tx['tx_hash'], status)) cursor.close()
def parse(db, tx, message, message_type_id): issuance_parse_cursor = db.cursor() # Unpack message. try: subasset_longname = None if message_type_id == SUBASSET_ID: if not util.enabled('subassets', block_index=tx['block_index']): logger.warn("subassets are not enabled at block %s" % tx['block_index']) raise exceptions.UnpackError # parse a subasset original issuance message asset_id, quantity, divisible, compacted_subasset_length = struct.unpack( SUBASSET_FORMAT, message[0:SUBASSET_FORMAT_LENGTH]) description_length = len( message) - SUBASSET_FORMAT_LENGTH - compacted_subasset_length if description_length < 0: logger.warn("invalid subasset length: [issuance] tx [%s]: %s" % (tx['tx_hash'], compacted_subasset_length)) raise exceptions.UnpackError messages_format = '>{}s{}s'.format(compacted_subasset_length, description_length) compacted_subasset_longname, description = struct.unpack( messages_format, message[SUBASSET_FORMAT_LENGTH:]) subasset_longname = util.expand_subasset_longname( compacted_subasset_longname) callable_, call_date, call_price = False, 0, 0.0 try: description = description.decode('utf-8') except UnicodeDecodeError: description = '' elif (tx['block_index'] > 283271 or config.TESTNET or config.REGTEST) and len(message) >= LENGTH_2: # Protocol change. if len(message) - LENGTH_2 <= 42: curr_format = FORMAT_2 + '{}p'.format(len(message) - LENGTH_2) else: curr_format = FORMAT_2 + '{}s'.format(len(message) - LENGTH_2) asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack( curr_format, message) call_price = round(call_price, 6) # TODO: arbitrary try: description = description.decode('utf-8') except UnicodeDecodeError: description = '' else: if len(message) != LENGTH_1: raise exceptions.UnpackError asset_id, quantity, divisible = struct.unpack(FORMAT_1, message) callable_, call_date, call_price, description = False, 0, 0.0, '' try: asset = util.generate_asset_name(asset_id, tx['block_index']) ##This is for backwards compatibility with assets names longer than 12 characters if asset.startswith('A'): namedAsset = util.get_asset_name(db, asset_id, tx['block_index']) if (namedAsset != 0): asset = namedAsset status = 'valid' except exceptions.AssetIDError: asset = None status = 'invalid: bad asset name' except exceptions.UnpackError as e: asset, quantity, divisible, callable_, call_date, call_price, description = None, None, None, None, None, None, None status = 'invalid: could not unpack' # parse and validate the subasset from the message subasset_parent = None if status == 'valid' and subasset_longname is not None: # Protocol change. try: # ensure the subasset_longname is valid util.validate_subasset_longname(subasset_longname) subasset_parent, subasset_longname = util.parse_subasset_from_asset_name( subasset_longname) except exceptions.AssetNameError as e: asset = None status = 'invalid: bad subasset name' reissuance = None fee = 0 if status == 'valid': call_date, call_price, problems, fee, description, divisible, reissuance, reissued_asset_longname = validate( db, tx['source'], tx['destination'], asset, quantity, divisible, callable_, call_date, call_price, description, subasset_parent, subasset_longname, block_index=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if not util.enabled('integer_overflow_fix', block_index=tx['block_index'] ) and 'total quantity overflow' in problems: quantity = 0 if tx['destination']: issuer = tx['destination'] transfer = True quantity = 0 else: issuer = tx['source'] transfer = False # Debit fee. if status == 'valid': util.debit(db, tx['source'], config.XCP, fee, action="issuance fee", event=tx['tx_hash']) # Lock? lock = False if status == 'valid': if description and description.lower() == 'lock': lock = True cursor = db.cursor() issuances = list( cursor.execute( '''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', asset))) cursor.close() description = issuances[-1][ 'description'] # Use last description. (Assume previous issuance exists because tx is valid.) timestamp, value_int, fee_fraction_int = None, None, None if not reissuance: # Add to table of assets. bindings = { 'asset_id': str(asset_id), 'asset_name': str(asset), 'block_index': tx['block_index'], 'asset_longname': subasset_longname, } sql = 'insert into assets values(:asset_id, :asset_name, :block_index, :asset_longname)' issuance_parse_cursor.execute(sql, bindings) if status == 'valid' and reissuance: # when reissuing, add the asset_longname to the issuances table for API lookups asset_longname = reissued_asset_longname else: asset_longname = subasset_longname # Add parsed transaction to message-type–specific table. bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'asset': asset, 'quantity': quantity, 'divisible': divisible, 'source': tx['source'], 'issuer': issuer, 'transfer': transfer, 'callable': callable_, 'call_date': call_date, 'call_price': call_price, 'description': description, 'fee_paid': fee, 'locked': lock, 'status': status, 'asset_longname': asset_longname, } if "integer overflow" not in status: sql = 'insert into issuances values(:tx_index, :tx_hash, 0, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname)' issuance_parse_cursor.execute(sql, bindings) else: logger.warn("Not storing [issuance] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # Credit. if status == 'valid' and quantity: util.credit(db, tx['source'], asset, quantity, action="issuance", event=tx['tx_hash']) issuance_parse_cursor.close()