def parse (db, tx, message): status = 'valid' try: asset, quantity = unpack(message, tx['block_index']) validate(db, tx['source'], tx['destination'], asset, quantity) util.debit(db, tx['source'], asset, quantity, 'destroy', tx['tx_hash']) except UnpackError as e: asset, quantity = None, None status = 'invalid: ' + ''.join(e.args) except (ValidateError, BalanceError) as e: status = 'invalid: ' + ''.join(e.args) finally: bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'quantity': quantity, 'tag': tag, 'status': status, } sql='insert into destructions values(:tx_index, :tx_hash, :block_index, :source, :asset, :quantity, :tag, :status)' cursor = db.cursor() cursor.execute(sql, bindings)
def parse(db, tx, message): status = 'valid' try: asset, quantity = unpack(message, tx['block_index']) validate(db, tx['source'], tx['destination'], asset, quantity) util.debit(db, tx['source'], asset, quantity, 'destroy', tx['tx_hash']) except UnpackError as e: asset, quantity = None, None status = 'invalid: ' + ''.join(e.args) except (ValidateError, BalanceError) as e: status = 'invalid: ' + ''.join(e.args) finally: bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'quantity': quantity, 'tag': tag, 'status': status, } sql = 'insert into destructions values(:tx_index, :tx_hash, :block_index, :source, :asset, :quantity, :tag, :status)' cursor = db.cursor() cursor.execute(sql, bindings)
def parse (db, tx, message): status = 'valid' asset, quantity, tag = None, None, None try: asset, quantity, tag = unpack(db, message) validate(db, tx['source'], tx['destination'], asset, quantity) util.debit(db, tx['source'], asset, quantity, 'destroy', tx['tx_hash']) except UnpackError as e: status = 'invalid: ' + ''.join(e.args) except (ValidateError, BalanceError) as e: status = 'invalid: ' + ''.join(e.args) bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'quantity': quantity, 'tag': tag, 'status': status, } if "integer overflow" not in status: sql = 'insert into destructions values(:tx_index, :tx_hash, :block_index, :source, :asset, :quantity, :tag, :status)' cursor = db.cursor() cursor.execute(sql, bindings) else: logger.warn("Not storing [destroy] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), ))
def parse(db, tx, message): status = 'valid' asset, quantity, tag = None, None, None try: asset, quantity, tag = unpack(db, message) validate(db, tx['source'], tx['destination'], asset, quantity) util.debit(db, tx['source'], asset, quantity, 'destroy', tx['tx_hash']) except UnpackError as e: status = 'invalid: ' + ''.join(e.args) except (ValidateError, BalanceError) as e: status = 'invalid: ' + ''.join(e.args) bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'quantity': quantity, 'tag': tag, 'status': status, } if "integer overflow" not in status: sql = 'insert into destructions values(:tx_index, :tx_hash, :block_index, :source, :asset, :quantity, :tag, :status)' cursor = db.cursor() cursor.execute(sql, bindings) else: logger.warn("Not storing [destroy] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), ))
def parse(db, tx, message): rps_parse_cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError (possible_moves, wager, move_random_hash, expiration) = struct.unpack(FORMAT, message) status = 'open' except (exceptions.UnpackError, struct.error): (possible_moves, wager, move_random_hash, expiration) = 0, 0, '', 0 status = 'invalid: could not unpack' if status == 'open': move_random_hash = binascii.hexlify(move_random_hash).decode('utf8') # Overbet rps_parse_cursor.execute( '''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], config.XCP)) balances = list(rps_parse_cursor) if not balances: wager = 0 else: balance = balances[0]['quantity'] if balance < wager: wager = balance problems = validate(db, tx['source'], possible_moves, wager, move_random_hash, expiration, tx['block_index']) if problems: status = 'invalid: {}'.format(', '.join(problems)) # Debit quantity wagered. (Escrow.) if status == 'open': util.debit(db, tx['source'], config.XCP, wager, action="open RPS", 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'], 'possible_moves': possible_moves, 'wager': wager, 'move_random_hash': move_random_hash, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'status': status, } sql = '''INSERT INTO rps VALUES (:tx_index, :tx_hash, :block_index, :source, :possible_moves, :wager, :move_random_hash, :expiration, :expire_index, :status)''' rps_parse_cursor.execute(sql, bindings) # Match. if status == 'open': match(db, tx, tx['block_index'])
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 expire (db, block_index): cursor = db.cursor() # Expire rps and give refunds for the quantity wager. cursor.execute('''SELECT * FROM rps WHERE (status = ? AND expire_index < ?)''', ('open', block_index)) for rps in cursor.fetchall(): cancel_rps(db, rps, 'expired', block_index) # Record rps expiration. bindings = { 'rps_index': rps['tx_index'], 'rps_hash': rps['tx_hash'], 'source': rps['source'], 'block_index': block_index } sql = '''INSERT INTO rps_expirations VALUES (:rps_index, :rps_hash, :source, :block_index)''' cursor.execute(sql, bindings) # Expire rps matches expire_bindings = ('pending', 'pending and resolved', 'resolved and pending', block_index) cursor.execute('''SELECT * FROM rps_matches WHERE (status IN (?, ?, ?) AND match_expire_index < ?)''', expire_bindings) for rps_match in cursor.fetchall(): new_rps_match_status = 'expired' # pending loses against resolved if rps_match['status'] == 'pending and resolved': new_rps_match_status = 'concluded: second player wins' elif rps_match['status'] == 'resolved and pending': new_rps_match_status = 'concluded: first player wins' update_rps_match_status(db, rps_match, new_rps_match_status, block_index) # Record rps match expiration. bindings = { 'rps_match_id': rps_match['id'], 'tx0_address': rps_match['tx0_address'], 'tx1_address': rps_match['tx1_address'], 'block_index': block_index } sql = '''INSERT INTO rps_match_expirations VALUES (:rps_match_id, :tx0_address, :tx1_address, :block_index)''' cursor.execute(sql, bindings) # Rematch not expired and not resolved RPS if new_rps_match_status == 'expired': sql = '''SELECT * FROM rps WHERE tx_hash IN (?, ?) AND status = ? AND expire_index >= ?''' bindings = (rps_match['tx0_hash'], rps_match['tx1_hash'], 'matched', block_index) matched_rps = list(cursor.execute(sql, bindings)) for rps in matched_rps: cursor.execute('''UPDATE rps SET status = ? WHERE tx_index = ?''', ('open', rps['tx_index'])) # Re-debit XCP refund by close_rps_match. util.debit(db, rps['source'], 'XCP', rps['wager'], action='reopen RPS after matching expiration', event=rps_match['id']) # Rematch match(db, {'tx_index': rps['tx_index']}, block_index) cursor.close()
def parse(db, tx, message): rps_parse_cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError (possible_moves, wager, move_random_hash, expiration) = struct.unpack(FORMAT, message) status = 'open' except (exceptions.UnpackError, struct.error): (possible_moves, wager, move_random_hash, expiration) = 0, 0, '', 0 status = 'invalid: could not unpack' if status == 'open': move_random_hash = binascii.hexlify(move_random_hash).decode('utf8') # Overbet rps_parse_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], 'XCP')) balances = list(rps_parse_cursor) if not balances: wager = 0 else: balance = balances[0]['quantity'] if balance < wager: wager = balance problems = validate(db, tx['source'], possible_moves, wager, move_random_hash, expiration, tx['block_index']) if problems: status = 'invalid: {}'.format(', '.join(problems)) # Debit quantity wagered. (Escrow.) if status == 'open': util.debit(db, tx['source'], 'XCP', wager, action="open RPS", 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'], 'possible_moves': possible_moves, 'wager': wager, 'move_random_hash': move_random_hash, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'status': status, } sql = '''INSERT INTO rps VALUES (:tx_index, :tx_hash, :block_index, :source, :possible_moves, :wager, :move_random_hash, :expiration, :expire_index, :status)''' rps_parse_cursor.execute(sql, bindings) # Match. if status == 'open': match(db, tx, tx['block_index']) rps_parse_cursor.close()
def transfer_value(self, tx, source, destination, quantity, asset=config.XCP): if source: util.debit(self.db, source, asset, quantity, action='transfer value', event=tx.tx_hash) if destination: util.credit(self.db, destination, asset, quantity, action='transfer value', event=tx.tx_hash) return True
def parse (db, tx, message): issuance_parse_cursor = db.cursor() # Unpack message. try: if (tx['block_index'] > 283271 or config.TESTNET) 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']) except exceptions.AssetNameError: asset = None status = 'invalid: bad asset name' status = 'valid' 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' fee = 0 if status == 'valid': call_date, call_price, problems, fee, description, divisible, reissuance = validate(db, tx['source'], tx['destination'], asset, quantity, divisible, callable_, call_date, call_price, description, 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'], } sql='insert into assets values(:asset_id, :asset_name, :block_index)' issuance_parse_cursor.execute(sql, bindings) # 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, } if "integer overflow" not in status: sql='insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status)' 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()
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) 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']) except exceptions.AssetNameError: asset = None status = 'invalid: bad asset name' status = 'valid' 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, :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()
def parse(db, tx, message): cursor = db.cursor() # Unpack message. try: unpacked = unpack(db, message, tx['block_index']) asset, quantity, destination, memo_bytes = unpacked['asset'], unpacked[ 'quantity'], unpacked['address'], unpacked['memo'] status = 'valid' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: asset, quantity, destination, memo_bytes = None, None, None, None status = 'invalid: could not unpack ({})'.format(e) except: asset, quantity, destination, memo_bytes = None, None, None, None status = 'invalid: could not unpack' if status == 'valid': # don't allow sends over MAX_INT at all if quantity and quantity > config.MAX_INT: status = 'invalid: quantity is too large' quantity = None if status == 'valid': problems = validate(db, tx['source'], destination, asset, quantity, memo_bytes, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': # verify balance is present cursor.execute( '''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], asset)) balances = cursor.fetchall() if not balances or balances[0]['quantity'] < quantity: status = 'invalid: insufficient funds' if status == 'valid': util.debit(db, tx['source'], asset, quantity, action='send', event=tx['tx_hash']) util.credit(db, destination, asset, quantity, action='send', event=tx['tx_hash']) # log invalid transactions if status != 'valid': if quantity is None: logger.warn("Invalid send from %s with status %s. (%s)" % (tx['source'], status, tx['tx_hash'])) else: logger.warn( "Invalid send of %s %s from %s to %s. status is %s. (%s)" % (quantity, asset, tx['source'], destination, status, 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'], 'destination': destination, 'asset': asset, 'quantity': quantity, 'status': status, 'memo': memo_bytes, } if "integer overflow" not in status and "quantity must be in satoshis" not in status: sql = 'insert into sends values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo)' cursor.execute(sql, bindings) else: logger.warn("Not storing [send] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) 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): issuance_parse_cursor = db.cursor() # Unpack message. try: if (tx['block_index'] > 283271 or config.TESTNET ) 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']) except exceptions.AssetNameError: asset = None status = 'invalid: bad asset name' status = 'valid' 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' fee = 0 if status == 'valid': call_date, call_price, problems, fee, description, divisible, reissuance = validate( db, tx['source'], tx['destination'], asset, quantity, divisible, callable_, call_date, call_price, description, block_index=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if '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'], } sql = 'insert into assets values(:asset_id, :asset_name, :block_index)' issuance_parse_cursor.execute(sql, bindings) # 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, } sql = 'insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status)' issuance_parse_cursor.execute(sql, bindings) # Credit. if status == 'valid' and quantity: util.credit(db, tx['source'], asset, quantity, action="issuance", event=tx['tx_hash']) issuance_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): cursor = db.cursor() fee_paid = round(config.UNIT/2) # Unpack message. try: unpacked = unpack(db, message, tx['block_index']) destination, flags, memo_bytes = unpacked['destination'], unpacked['flags'], unpacked['memo'] status = 'valid' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: destination, flags, memo_bytes = None, None, None status = 'invalid: could not unpack ({})'.format(e) except BalanceError: destination, flags, memo_bytes = None, None, None status = 'invalid: insufficient balance for antispam fee for sweep' except Exception as err: destination, flags, memo_bytes = None, None, None status = 'invalid: could not unpack, ' + str(err) if status == 'valid': problems = validate(db, tx['source'], destination, flags, memo_bytes, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) try: util.debit(db, tx['source'], config.XCP, fee_paid, action='sweep fee', event=tx['tx_hash']) except BalanceError: destination, flags, memo_bytes = None, None, None status = 'invalid: insufficient balance for antispam fee for sweep' if status == 'valid': cursor.execute('''SELECT * FROM balances WHERE address = ?''', (tx['source'],)) balances = cursor.fetchall() if flags & FLAG_BALANCES: for balance in balances: util.debit(db, tx['source'], balance['asset'], balance['quantity'], action='sweep', event=tx['tx_hash']) util.credit(db, destination, balance['asset'], balance['quantity'], action='sweep', event=tx['tx_hash']) if flags & FLAG_OWNERSHIP: for balance in balances: cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', balance['asset'])) issuances = cursor.fetchall() if len(issuances) > 0: last_issuance = issuances[-1] if last_issuance['issuer'] == tx['source']: bindings= { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'asset': balance['asset'], 'quantity': 0, 'divisible': last_issuance['divisible'], 'source': last_issuance['source'], 'issuer': destination, 'transfer': True, 'callable': last_issuance['callable'], 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'fee_paid': 0, 'locked': last_issuance['locked'], 'status': status, 'asset_longname': last_issuance['asset_longname'], } sql='insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname)' cursor.execute(sql, bindings) bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'destination': destination, 'flags': flags, 'status': status, 'memo': memo_bytes, 'fee_paid': fee_paid } sql = 'insert into sweeps values(:tx_index, :tx_hash, :block_index, :source, :destination, :flags, :status, :memo, :fee_paid)' cursor.execute(sql, bindings) cursor.close()
def parse (db, tx, message): bet_parse_cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration) = struct.unpack(FORMAT, message) status = 'open' except (exceptions.UnpackError, struct.error): (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, fee_fraction_int) = 0, 0, 0, 0, 0, 0, 0, 0 status = 'invalid: could not unpack' odds, fee_fraction = 0, 0 feed_address = tx['destination'] if status == 'open': try: odds = util.price(wager_quantity, counterwager_quantity) except ZeroDivisionError: odds = 0 fee_fraction = get_fee_fraction(db, feed_address) # Overbet bet_parse_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], config.XCP)) balances = list(bet_parse_cursor) if not balances: wager_quantity = 0 else: balance = balances[0]['quantity'] if balance < wager_quantity: wager_quantity = balance counterwager_quantity = int(util.price(wager_quantity, odds)) problems, leverage = validate(db, tx['source'], feed_address, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) # Debit quantity wagered. (Escrow.) if status == 'open': util.debit(db, tx['source'], config.XCP, wager_quantity, action='bet', 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'], 'feed_address': feed_address, 'bet_type': bet_type, 'deadline': deadline, 'wager_quantity': wager_quantity, 'wager_remaining': wager_quantity, 'counterwager_quantity': counterwager_quantity, 'counterwager_remaining': counterwager_quantity, 'target_value': target_value, 'leverage': leverage, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'fee_fraction_int': fee_fraction * 1e8, 'status': status, } if "integer overflow" not in status: sql = 'insert into bets values(:tx_index, :tx_hash, :block_index, :source, :feed_address, :bet_type, :deadline, :wager_quantity, :wager_remaining, :counterwager_quantity, :counterwager_remaining, :target_value, :leverage, :expiration, :expire_index, :fee_fraction_int, :status)' bet_parse_cursor.execute(sql, bindings) else: logger.warn("Not storing [bet] 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) bet_parse_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']) 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, :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()
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): cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError assetid, give_quantity, escrow_quantity, mainchainrate, dispenser_status = struct.unpack(FORMAT, message) asset = util.generate_asset_name(assetid, util.CURRENT_BLOCK_INDEX) status = 'valid' except (exceptions.UnpackError, struct.error): assetid, give_quantity, mainchainrate, asset = None, None, None, None status = 'invalid: could not unpack' if status == 'valid': assetid, problems = validate (db, tx['source'], asset, give_quantity, escrow_quantity, mainchainrate, dispenser_status, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': if dispenser_status == STATUS_OPEN: cursor.execute('SELECT * FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', { 'source': tx['source'], 'asset': asset, 'status': STATUS_OPEN }) existing = cursor.fetchall() if len(existing) == 0: # Create the new dispenser bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'give_quantity': give_quantity, 'escrow_quantity': escrow_quantity, 'satoshirate': mainchainrate, 'status': dispenser_status, 'give_remaining': escrow_quantity } try: util.debit(db, tx['source'], asset, escrow_quantity, action='open dispenser', event=tx['tx_hash']) sql = 'insert into dispensers values(:tx_index, :tx_hash, :block_index, :source, :asset, :give_quantity, :escrow_quantity, :satoshirate, :status, :give_remaining)' cursor.execute(sql, bindings) except (util.DebitError): status = 'insufficient funds' elif len(existing) == 1 and existing[0]['satoshirate'] == mainchainrate and existing[0]['give_quantity'] == give_quantity: # Refill the dispenser by the given amount bindings = { 'source': tx['source'], 'asset': asset, 'give_remaining': existing[0]['give_remaining'] + escrow_quantity, 'status': STATUS_OPEN, 'block_index': tx['block_index'] } try: util.debit(db, tx['source'], asset, escrow_quantity, action='refill dispenser', event=tx['tx_hash']) sql = 'UPDATE dispensers SET give_remaining=:give_remaining \ WHERE source=:source AND asset=:asset AND status=:status' cursor.execute(sql, bindings) except (util.DebitError): status = 'insufficient funds' else: status = 'can only have one open dispenser per asset per address' elif dispenser_status == STATUS_CLOSED: cursor.execute('SELECT give_remaining FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', { 'source': tx['source'], 'asset': asset, 'status': STATUS_OPEN }) existing = cursor.fetchall() if len(existing) == 1: util.credit(db, tx['source'], asset, existing[0]['give_remaining'], action='close dispenser', event=tx['tx_hash']) bindings = { 'source': tx['source'], 'asset': asset, 'status': STATUS_CLOSED, 'block_index': tx['block_index'] } sql = 'UPDATE dispensers SET give_remaining=0, status=:status WHERE source=:source AND asset=:asset' cursor.execute(sql, bindings) else: status = 'dispenser inexistent' else: status = 'invalid: status must be one of OPEN or CLOSE'
def parse (db, tx, message): cursor = db.cursor() # Unpack message. try: unpacked = unpack(db, message, tx['block_index']) asset, quantity, destination, memo_bytes = unpacked['asset'], unpacked['quantity'], unpacked['address'], unpacked['memo'] status = 'valid' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: asset, quantity, destination, memo_bytes = None, None, None, None status = 'invalid: could not unpack ({})'.format(e) except: asset, quantity, destination, memo_bytes = None, None, None, None status = 'invalid: could not unpack' if status == 'valid': # don't allow sends over MAX_INT at all if quantity and quantity > config.MAX_INT: status = 'invalid: quantity is too large' quantity = None if status == 'valid': problems = validate(db, tx['source'], destination, asset, quantity, memo_bytes, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': # verify balance is present cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], asset)) balances = cursor.fetchall() if not balances or balances[0]['quantity'] < quantity: status = 'invalid: insufficient funds' if status == 'valid': util.debit(db, tx['source'], asset, quantity, action='send', event=tx['tx_hash']) util.credit(db, destination, asset, quantity, action='send', event=tx['tx_hash']) # log invalid transactions if status != 'valid': if quantity is None: logger.warn("Invalid send from %s with status %s. (%s)" % (tx['source'], status, tx['tx_hash'])) else: logger.warn("Invalid send of %s %s from %s to %s. status is %s. (%s)" % (quantity, asset, tx['source'], destination, status, 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'], 'destination': destination, 'asset': asset, 'quantity': quantity, 'status': status, 'memo': memo_bytes, } if "integer overflow" not in status and "quantity must be in satoshis" not in status: sql = 'insert into sends values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo)' cursor.execute(sql, bindings) else: logger.warn("Not storing [send] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) cursor.close()
def parse(db, tx, message): cursor = db.cursor() # Unpack message. try: action_address = tx['source'] assetid, give_quantity, escrow_quantity, mainchainrate, dispenser_status = struct.unpack( FORMAT, message[0:LENGTH]) if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: action_address = address.unpack(message[LENGTH:LENGTH + 21]) asset = util.generate_asset_name(assetid, util.CURRENT_BLOCK_INDEX) status = 'valid' except (exceptions.UnpackError, struct.error) as e: assetid, give_quantity, mainchainrate, asset = None, None, None, None status = 'invalid: could not unpack' if status == 'valid': if dispenser_status == STATUS_OPEN or dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: cursor.execute( 'SELECT * FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', { 'source': action_address, 'asset': asset, 'status': STATUS_OPEN }) existing = cursor.fetchall() if len(existing) == 0: # Create the new dispenser try: if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: cursor.execute( 'SELECT count(*) cnt FROM balances WHERE address=:address AND quantity > 0', {'address': action_address}) counts = cursor.fetchall()[0] if counts['cnt'] == 0: util.debit(db, tx['source'], asset, escrow_quantity, action='open dispenser empty addr', event=tx['tx_hash']) util.credit(db, action_address, asset, escrow_quantity, action='open dispenser empty addr', event=tx['tx_hash']) util.debit(db, action_address, asset, escrow_quantity, action='open dispenser empty addr', event=tx['tx_hash']) else: status = 'invalid: address not empty' else: util.debit(db, tx['source'], asset, escrow_quantity, action='open dispenser', event=tx['tx_hash']) except util.DebitError as e: status = 'invalid: insufficient funds' if status == 'valid': bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': action_address, 'asset': asset, 'give_quantity': give_quantity, 'escrow_quantity': escrow_quantity, 'satoshirate': mainchainrate, 'status': STATUS_OPEN, 'give_remaining': escrow_quantity } sql = 'insert into dispensers values(:tx_index, :tx_hash, :block_index, :source, :asset, :give_quantity, :escrow_quantity, :satoshirate, :status, :give_remaining)' cursor.execute(sql, bindings) elif len(existing) == 1 and existing[0][ 'satoshirate'] == mainchainrate and existing[0][ 'give_quantity'] == give_quantity: if tx["source"] == action_address: # Refill the dispenser by the given amount bindings = { 'source': tx['source'], 'asset': asset, 'status': dispenser_status, 'give_remaining': existing[0]['give_remaining'] + escrow_quantity, 'status': STATUS_OPEN, 'block_index': tx['block_index'] } try: util.debit(db, tx['source'], asset, escrow_quantity, action='refill dispenser', event=tx['tx_hash']) sql = 'UPDATE dispensers SET give_remaining=:give_remaining \ WHERE source=:source AND asset=:asset AND status=:status' cursor.execute(sql, bindings) except (util.DebitError): status = 'insufficient funds' else: status = 'invalid: can only refill dispenser from source' else: status = 'can only have one open dispenser per asset per address' elif dispenser_status == STATUS_CLOSED: cursor.execute( 'SELECT give_remaining FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', { 'source': tx['source'], 'asset': asset, 'status': STATUS_OPEN }) existing = cursor.fetchall() if len(existing) == 1: util.credit(db, tx['source'], asset, existing[0]['give_remaining'], action='close dispenser', event=tx['tx_hash']) bindings = { 'source': tx['source'], 'asset': asset, 'status': STATUS_CLOSED, 'block_index': tx['block_index'] } sql = 'UPDATE dispensers SET give_remaining=0, status=:status WHERE source=:source AND asset=:asset' cursor.execute(sql, bindings) else: status = 'dispenser inexistent' else: status = 'invalid: status must be one of OPEN or CLOSE' if status != 'valid': logger.warn("Not storing [dispenser] tx [%s]: %s" % (tx['tx_hash'], status)) cursor.close()
def parse(db, tx, message): bet_parse_cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration) = struct.unpack(FORMAT, message) status = 'open' except (exceptions.UnpackError, struct.error): (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, fee_fraction_int) = 0, 0, 0, 0, 0, 0, 0, 0 status = 'invalid: could not unpack' odds, fee_fraction = 0, 0 feed_address = tx['destination'] if status == 'open': try: odds = util.price(wager_quantity, counterwager_quantity) except ZeroDivisionError: odds = 0 fee_fraction = get_fee_fraction(db, feed_address) # Overbet bet_parse_cursor.execute( '''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], config.XCP)) balances = list(bet_parse_cursor) if not balances: wager_quantity = 0 else: balance = balances[0]['quantity'] if balance < wager_quantity: wager_quantity = balance counterwager_quantity = int(util.price(wager_quantity, odds)) problems, leverage = validate(db, tx['source'], feed_address, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) # Debit quantity wagered. (Escrow.) if status == 'open': util.debit(db, tx['source'], config.XCP, wager_quantity, action='bet', 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'], 'feed_address': feed_address, 'bet_type': bet_type, 'deadline': deadline, 'wager_quantity': wager_quantity, 'wager_remaining': wager_quantity, 'counterwager_quantity': counterwager_quantity, 'counterwager_remaining': counterwager_quantity, 'target_value': target_value, 'leverage': leverage, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'fee_fraction_int': fee_fraction * 1e8, 'status': status, } if "integer overflow" not in status: sql = 'insert into bets values(:tx_index, :tx_hash, :block_index, :source, :feed_address, :bet_type, :deadline, :wager_quantity, :wager_remaining, :counterwager_quantity, :counterwager_remaining, :target_value, :leverage, :expiration, :expire_index, :fee_fraction_int, :status)' bet_parse_cursor.execute(sql, bindings) else: logger.warn("Not storing [bet] 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) bet_parse_cursor.close()