def set_storage_data(self, contract_id, key, value): # NOTE: This could all be done more elegantly, I think. key = key.to_bytes(32, byteorder='big') value = value.to_bytes(32, byteorder='big') cursor = self.db.cursor() cursor.execute('''SELECT * FROM storage WHERE contract_id = ? AND key = ?''', (contract_id, key)) storages = list(cursor) if storages: # Update value. bindings = { 'contract_id': contract_id, 'key': key, 'value': value } log.message(self.db, self.number, 'update', 'storage', bindings) sql='''UPDATE storage SET value = :value WHERE contract_id = :contract_id AND key = :key''' cursor.execute(sql, bindings) else: # Insert value. bindings = { 'contract_id': contract_id, 'key': key, 'value': value } log.message(self.db, self.number, 'insert', 'storage', bindings) sql='''INSERT INTO storage VALUES (:contract_id, :key, :value)''' cursor.execute(sql, bindings) storages = cursor.execute('''SELECT * FROM storage WHERE contract_id = ? AND key = ?''', (contract_id, key)) return value
def cancel_bet_match(db, bet_match, status, block_index): # Does not re‐open, re‐fill, etc. constituent bets. cursor = db.cursor() # Recredit tx0 address. util.credit(db, block_index, bet_match['tx0_address'], config.XCP, bet_match['forward_quantity'], action='recredit forward quantity', event=bet_match['id']) # Recredit tx1 address. util.credit(db, block_index, bet_match['tx1_address'], config.XCP, bet_match['backward_quantity'], action='recredit backward quantity', event=bet_match['id']) # Update status of bet match. bindings = {'status': status, 'bet_match_id': bet_match['id']} sql = 'update bet_matches set status = :status where id = :bet_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'bet_matches', bindings) cursor.close()
def cancel_order (db, order, status, block_index): cursor = db.cursor() # Update status of order. bindings = { 'status': status, 'tx_hash': order['tx_hash'] } sql='update orders set status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) if order['give_asset'] != config.BTC: # Can’t credit BTC. util.credit(db, block_index, order['source'], order['give_asset'], order['give_remaining'], action='cancel order', event=order['tx_hash']) if status == 'expired': # Record offer expiration. bindings = { 'order_index': order['tx_index'], 'order_hash': order['tx_hash'], 'source': order['source'], 'block_index': block_index } sql='insert into order_expirations values(:order_index, :order_hash, :source, :block_index)' cursor.execute(sql, bindings) cursor.close()
def set_storage_data(self, contract_id, key, value): # NOTE: This could all be done more elegantly, I think. key = key.to_bytes(32, byteorder='big') value = value.to_bytes(32, byteorder='big') cursor = self.db.cursor() cursor.execute( '''SELECT * FROM storage WHERE contract_id = ? AND key = ?''', (contract_id, key)) storages = list(cursor) if storages: # Update value. bindings = {'contract_id': contract_id, 'key': key, 'value': value} log.message(self.db, self.number, 'update', 'storage', bindings) sql = '''UPDATE storage SET value = :value WHERE contract_id = :contract_id AND key = :key''' cursor.execute(sql, bindings) else: # Insert value. bindings = {'contract_id': contract_id, 'key': key, 'value': value} log.message(self.db, self.number, 'insert', 'storage', bindings) sql = '''INSERT INTO storage VALUES (:contract_id, :key, :value)''' cursor.execute(sql, bindings) storages = cursor.execute( '''SELECT * FROM storage WHERE contract_id = ? AND key = ?''', (contract_id, key)) return value
def update_rps_match_status (db, rps_match, status, block_index): cursor = db.cursor() if status in ['expired', 'concluded: tie']: # Recredit tx0 address. util.credit(db, block_index, rps_match['tx0_address'], 'XCP', rps_match['wager'], action='recredit wager', event=rps_match['id']) # Recredit tx1 address. util.credit(db, block_index, rps_match['tx1_address'], 'XCP', rps_match['wager'], action='recredit wager', event=rps_match['id']) elif status.startswith('concluded'): # Credit the winner winner = rps_match['tx0_address'] if status == 'concluded: first player wins' else rps_match['tx1_address'] util.credit(db, block_index, winner, 'XCP', 2 * rps_match['wager'], action='wins', event=rps_match['id']) # Update status of rps match. bindings = { 'status': status, 'rps_match_id': rps_match['id'] } sql='UPDATE rps_matches SET status = :status WHERE id = :rps_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'rps_matches', bindings) cursor.close()
def del_account(self, suicide): cursor = self.db.cursor() contract_id = suicide['contract_id'] logger.debug('SUICIDING {}'.format(contract_id)) bindings = {'contract_id': contract_id} log.message(self.db, self.number, 'delete', 'contracts', bindings) cursor.execute('''DELETE FROM contracts WHERE contract_id = :contract_id''', bindings) log.message(self.db, self.number, 'delete', 'storage', bindings) cursor.execute('''DELETE FROM storage WHERE contract_id = :contract_id''', bindings)
def parse (db, tx, message): cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError tx0_hash_bytes, tx1_hash_bytes = struct.unpack(FORMAT, message) tx0_hash, tx1_hash = binascii.hexlify(tx0_hash_bytes).decode('utf-8'), binascii.hexlify(tx1_hash_bytes).decode('utf-8') order_match_id = util.make_id(tx0_hash, tx1_hash) status = 'valid' except (exceptions.UnpackError, struct.error) as e: tx0_hash, tx1_hash, order_match_id = None, None, None status = 'invalid: could not unpack' if status == 'valid': destination, btc_quantity, escrowed_asset, escrowed_quantity, order_match, problems = validate(db, tx['source'], order_match_id, tx['block_index']) if problems: order_match = None status = 'invalid: ' + '; '.join(problems) if status == 'valid': # BTC must be paid all at once. if tx['btc_amount'] >= btc_quantity: # Credit source address for the currency that he bought with the bitcoins. util.credit(db, tx['block_index'], tx['source'], escrowed_asset, escrowed_quantity, action='btcpay', event=tx['tx_hash']) status = 'valid' # Update order match. bindings = { 'status': 'completed', 'order_match_id': order_match_id } sql='update order_matches set status = :status where id = :order_match_id' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'order_matches', 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'], 'source': tx['source'], 'destination': tx['destination'], 'btc_amount': tx['btc_amount'], 'order_match_id': order_match_id, 'status': status, } sql='insert into btcpays values(:tx_index, :tx_hash, :block_index, :source, :destination, :btc_amount, :order_match_id, :status)' cursor.execute(sql, bindings) cursor.close()
def set_nonce(self, address, nonce): cursor = self.db.cursor() cursor.execute('''SELECT * FROM nonces WHERE (address = :address)''', {'address': address}) nonces = list(cursor) bindings = {'address': address, 'nonce': nonce} if not nonces: log.message(self.db, self.number, 'insert', 'nonces', bindings) cursor.execute('''INSERT INTO nonces VALUES(:address, :nonce)''', bindings) else: log.message(self.db, self.number, 'update', 'nonces', bindings) cursor.execute('''UPDATE nonces SET nonce = :nonce WHERE (address = :address)''', bindings)
def del_account(self, contract_id): cursor = self.db.cursor() logger.debug('SUICIDING {}'.format(contract_id)) bindings = {'contract_id': contract_id} log.message(self.db, self.number, 'delete', 'contracts', bindings) cursor.execute( '''DELETE FROM contracts WHERE contract_id = :contract_id''', bindings) log.message(self.db, self.number, 'delete', 'storage', bindings) cursor.execute( '''DELETE FROM storage WHERE contract_id = :contract_id''', bindings)
def cancel_bet (db, bet, status, block_index): cursor = db.cursor() # Update status of bet. bindings = { 'status': status, 'tx_hash': bet['tx_hash'] } sql='update bets set status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'bets', bindings) util.credit(db, block_index, bet['source'], config.XCP, bet['wager_remaining'], action='recredit wager remaining', event=bet['tx_hash']) cursor = db.cursor()
def cancel_rps (db, rps, status, block_index): cursor = db.cursor() # Update status of rps. bindings = { 'status': status, 'tx_hash': rps['tx_hash'] } sql='''UPDATE rps SET status = :status WHERE tx_hash = :tx_hash''' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'rps', bindings) util.credit(db, block_index, rps['source'], 'XCP', rps['wager'], action='recredit wager', event=rps['tx_hash']) cursor.close()
def set_nonce(self, address, nonce): cursor = self.db.cursor() cursor.execute('''SELECT * FROM nonces WHERE (address = :address)''', {'address': address}) nonces = list(cursor) bindings = {'address': address, 'nonce': nonce} if not nonces: log.message(self.db, self.number, 'insert', 'nonces', bindings) cursor.execute('''INSERT INTO nonces VALUES(:address, :nonce)''', bindings) else: log.message(self.db, self.number, 'update', 'nonces', bindings) cursor.execute( '''UPDATE nonces SET nonce = :nonce WHERE (address = :address)''', bindings)
def cancel_bet(db, bet, status, block_index): cursor = db.cursor() # Update status of bet. bindings = {'status': status, 'tx_hash': bet['tx_hash']} sql = 'update bets set status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'bets', bindings) util.credit(db, block_index, bet['source'], config.XCP, bet['wager_remaining'], action='recredit wager remaining', event=bet['tx_hash']) cursor = db.cursor()
def exectracer(cursor, sql, bindings): # This means that all changes to database must use a very simple syntax. # TODO: Need sanity checks here. sql = sql.lower() # Parse SQL. array = sql.split('(')[0].split(' ') command = array[0] if 'insert' in sql: category = array[2] elif 'update' in sql: category = array[1] else: return True db = cursor.getconnection() dictionary = { 'command': command, 'category': category, 'bindings': bindings } # Skip blocks, transactions. if 'blocks' in sql or 'transactions' in sql: return True # Record alteration in database. if category not in ('balances', 'messages', 'mempool', 'assets'): if category not in ('suicides', 'postqueue'): # These tables are ephemeral. if category not in ('nonces', 'storage'): # List message manually. if not (command in ('update') and category in ('orders', 'bets', 'rps', 'order_matches', 'bet_matches', 'rps_matches', 'contracts')): # List message manually. # try: log.message(db, bindings['block_index'], command, category, bindings) # except: # raise TypeError('SQLite3 statements must used named arguments.') return True
def cancel_bet_match (db, bet_match, status, block_index): # Does not re‐open, re‐fill, etc. constituent bets. cursor = db.cursor() # Recredit tx0 address. util.credit(db, block_index, bet_match['tx0_address'], config.XCP, bet_match['forward_quantity'], action='recredit forward quantity', event=bet_match['id']) # Recredit tx1 address. util.credit(db, block_index, bet_match['tx1_address'], config.XCP, bet_match['backward_quantity'], action='recredit backward quantity', event=bet_match['id']) # Update status of bet match. bindings = { 'status': status, 'bet_match_id': bet_match['id'] } sql='update bet_matches set status = :status where id = :bet_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'bet_matches', bindings) cursor.close()
def match(db, tx): cursor = db.cursor() # Get bet in question. bets = list( cursor.execute( '''SELECT * FROM bets\ WHERE (tx_index = ? AND status = ?)''', (tx['tx_index'], 'open'))) if not bets: cursor.close() return else: assert len(bets) == 1 tx1 = bets[0] # Get counterbet_type. if tx1['bet_type'] % 2: counterbet_type = tx1['bet_type'] - 1 else: counterbet_type = tx1['bet_type'] + 1 feed_address = tx1['feed_address'] cursor.execute( '''SELECT * FROM bets\ WHERE (feed_address=? AND status=? AND bet_type=?)''', (tx1['feed_address'], 'open', counterbet_type)) tx1_wager_remaining = tx1['wager_remaining'] tx1_counterwager_remaining = tx1['counterwager_remaining'] bet_matches = cursor.fetchall() if tx['block_index'] > 284500 or config.TESTNET: # Protocol change. sorted(bet_matches, key=lambda x: x['tx_index']) # Sort by tx index second. sorted(bet_matches, key=lambda x: util.price(x['wager_quantity'], x[ 'counterwager_quantity'], tx1['block_index']) ) # Sort by price first. tx1_status = tx1['status'] for tx0 in bet_matches: if tx1_status != 'open': break logger.debug('Considering: ' + tx0['tx_hash']) tx0_wager_remaining = tx0['wager_remaining'] tx0_counterwager_remaining = tx0['counterwager_remaining'] # Bet types must be opposite. if counterbet_type != tx0['bet_type']: logger.debug('Skipping: bet types disagree.') continue # Leverages must agree exactly if tx0['leverage'] != tx1['leverage']: logger.debug('Skipping: leverages disagree.') continue # Target values must agree exactly. if tx0['target_value'] != tx1['target_value']: logger.debug('Skipping: target values disagree.') continue # Fee fractions must agree exactly. if tx0['fee_fraction_int'] != tx1['fee_fraction_int']: logger.debug('Skipping: fee fractions disagree.') continue # Deadlines must agree exactly. if tx0['deadline'] != tx1['deadline']: logger.debug('Skipping: deadlines disagree.') continue # If the odds agree, make the trade. The found order sets the odds, # and they trade as much as they can. tx0_odds = util.price(tx0['wager_quantity'], tx0['counterwager_quantity'], tx1['block_index']) tx0_inverse_odds = util.price(tx0['counterwager_quantity'], tx0['wager_quantity'], tx1['block_index']) tx1_odds = util.price(tx1['wager_quantity'], tx1['counterwager_quantity'], tx1['block_index']) if tx['block_index'] < 286000: tx0_inverse_odds = util.price( 1, tx0_odds, tx1['block_index']) # Protocol change. logger.debug('Tx0 Inverse Odds: {}; Tx1 Odds: {}'.format( float(tx0_inverse_odds), float(tx1_odds))) if tx0_inverse_odds > tx1_odds: logger.debug('Skipping: price mismatch.') else: logger.debug('Potential forward quantities: {}, {}'.format( tx0_wager_remaining, int( util.price(tx1_wager_remaining, tx1_odds, tx1['block_index'])))) forward_quantity = int( min( tx0_wager_remaining, int( util.price(tx1_wager_remaining, tx1_odds, tx1['block_index'])))) logger.debug('Forward Quantity: {}'.format(forward_quantity)) backward_quantity = round(forward_quantity / tx0_odds) logger.debug('Backward Quantity: {}'.format(backward_quantity)) if not forward_quantity: logger.debug('Skipping: zero forward quantity.') continue if tx1['block_index'] >= 286500 or config.TESTNET: # Protocol change. if not backward_quantity: logger.debug('Skipping: zero backward quantity.') continue bet_match_id = util.make_id(tx0['tx_hash'], tx1['tx_hash']) # Debit the order. # Counterwager remainings may be negative. tx0_wager_remaining = tx0_wager_remaining - forward_quantity tx0_counterwager_remaining = tx0_counterwager_remaining - backward_quantity tx1_wager_remaining = tx1_wager_remaining - backward_quantity tx1_counterwager_remaining = tx1_counterwager_remaining - forward_quantity # tx0 tx0_status = 'open' if tx0_wager_remaining <= 0 or tx0_counterwager_remaining <= 0: # Fill order, and recredit give_remaining. tx0_status = 'filled' util.credit(db, tx1['block_index'], tx0['source'], config.XCP, tx0_wager_remaining, event=tx1['tx_hash'], action='filled') bindings = { 'wager_remaining': tx0_wager_remaining, 'counterwager_remaining': tx0_counterwager_remaining, 'status': tx0_status, 'tx_hash': tx0['tx_hash'] } sql = 'update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, tx1['block_index'], 'update', 'bets', bindings) if tx1['block_index'] >= 292000 or config.TESTNET: # Protocol change if tx1_wager_remaining <= 0 or tx1_counterwager_remaining <= 0: # Fill order, and recredit give_remaining. tx1_status = 'filled' util.credit(db, tx1['block_index'], tx1['source'], config.XCP, tx1_wager_remaining, event=tx1['tx_hash'], action='filled') # tx1 bindings = { 'wager_remaining': tx1_wager_remaining, 'counterwager_remaining': tx1_counterwager_remaining, 'status': tx1_status, 'tx_hash': tx1['tx_hash'] } sql = 'update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, tx1['block_index'], 'update', 'bets', bindings) # Get last value of feed. broadcasts = list( cursor.execute( '''SELECT * FROM broadcasts WHERE (status = ? AND source = ?) ORDER BY tx_index ASC''', ('valid', feed_address))) initial_value = broadcasts[-1]['value'] # Record bet fulfillment. bindings = { 'id': util.make_id(tx0['tx_hash'], tx['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_hash'], 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'tx0_bet_type': tx0['bet_type'], 'tx1_bet_type': tx1['bet_type'], 'feed_address': tx1['feed_address'], 'initial_value': initial_value, 'deadline': tx1['deadline'], 'target_value': tx1['target_value'], 'leverage': tx1['leverage'], 'forward_quantity': forward_quantity, 'backward_quantity': backward_quantity, 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': max(tx0['block_index'], tx1['block_index']), 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': min(tx0['expire_index'], tx1['expire_index']), 'fee_fraction_int': tx1['fee_fraction_int'], 'status': 'pending', } sql = 'insert into bet_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :tx0_bet_type, :tx1_bet_type, :feed_address, :initial_value, :deadline, :target_value, :leverage, :forward_quantity, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_fraction_int, :status)' cursor.execute(sql, bindings) cursor.close() return
def match (db, tx, block_index): cursor = db.cursor() # Get rps in question. rps = list(cursor.execute('''SELECT * FROM rps WHERE tx_index = ? AND status = ?''', (tx['tx_index'], 'open'))) if not rps: cursor.close() return else: assert len(rps) == 1 tx1 = rps[0] possible_moves = tx1['possible_moves'] wager = tx1['wager'] tx1_status = 'open' # Get rps match bindings = (possible_moves, 'open', wager, tx1['source']) # dont match twice same RPS already_matched = [] old_rps_matches = cursor.execute('''SELECT * FROM rps_matches WHERE tx0_hash = ? OR tx1_hash = ?''', (tx1['tx_hash'], tx1['tx_hash'])) for old_rps_match in old_rps_matches: counter_tx_hash = old_rps_match['tx1_hash'] if tx1['tx_hash'] == old_rps_match['tx0_hash'] else old_rps_match['tx0_hash'] already_matched.append(counter_tx_hash) already_matched_cond = '' if already_matched: already_matched_cond = '''AND tx_hash NOT IN ({})'''.format(','.join(['?' for e in range(0, len(already_matched))])) bindings += tuple(already_matched) sql = '''SELECT * FROM rps WHERE (possible_moves = ? AND status = ? AND wager = ? AND source != ? {}) ORDER BY tx_index LIMIT 1'''.format(already_matched_cond) rps_matches = list(cursor.execute(sql, bindings)) if rps_matches: tx0 = rps_matches[0] # update status for txn in [tx0, tx1]: bindings = { 'status': 'matched', 'tx_index': txn['tx_index'] } cursor.execute('''UPDATE rps SET status = :status WHERE tx_index = :tx_index''', bindings) log.message(db, block_index, 'update', 'rps', bindings) bindings = { 'id': util.make_id(tx0['tx_hash'], tx1['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_hash'], 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'tx0_move_random_hash': tx0['move_random_hash'], 'tx1_move_random_hash': tx1['move_random_hash'], 'wager': wager, 'possible_moves': possible_moves, 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': block_index, 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': block_index + 20, 'status': 'pending' } sql = '''INSERT INTO rps_matches VALUES (:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :tx0_move_random_hash, :tx1_move_random_hash, :wager, :possible_moves, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :status)''' cursor.execute(sql, bindings) cursor.close()
def cancel_order_match (db, order_match, status, block_index): '''The only cancelling is an expiration. ''' cursor = db.cursor() # Skip order matches just expired as a penalty. (Not very efficient.) if not (block_index >= 314250 or config.TESTNET): # Protocol change. order_matches = list(cursor.execute('''SELECT * FROM order_matches \ WHERE (id = ? AND status = ?)''', (order_match['id'], 'expired'))) if order_matches: cursor.close() return # Update status of order match. bindings = { 'status': status, 'order_match_id': order_match['id'] } sql='update order_matches set status = :status where id = :order_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'order_matches', bindings) order_match_id = util.make_id(order_match['tx0_hash'], order_match['tx1_hash']) # If tx0 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. orders = list(cursor.execute('''SELECT * FROM orders \ WHERE tx_index = ?''', (order_match['tx0_index'],))) assert len(orders) == 1 tx0_order = orders[0] if tx0_order['status'] in ('expired', 'cancelled'): tx0_order_status = tx0_order['status'] if order_match['forward_asset'] != config.BTC: util.credit(db, block_index, order_match['tx0_address'], order_match['forward_asset'], order_match['forward_quantity'], action='order {}'.format(tx0_order_status), event=order_match['id']) else: tx0_give_remaining = tx0_order['give_remaining'] + order_match['forward_quantity'] tx0_get_remaining = tx0_order['get_remaining'] + order_match['backward_quantity'] if tx0_order['get_asset'] == config.BTC and (block_index >= 297000 or config.TESTNET): # Protocol change. tx0_fee_required_remaining = tx0_order['fee_required_remaining'] + order_match['fee_paid'] else: tx0_fee_required_remaining = tx0_order['fee_required_remaining'] tx0_order_status = tx0_order['status'] bindings = { 'give_remaining': tx0_give_remaining, 'get_remaining': tx0_get_remaining, 'status': tx0_order_status, 'fee_required_remaining': tx0_fee_required_remaining, 'tx_hash': order_match['tx0_hash'] } sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) # If tx1 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. orders = list(cursor.execute('''SELECT * FROM orders \ WHERE tx_index = ?''', (order_match['tx1_index'],))) assert len(orders) == 1 tx1_order = orders[0] if tx1_order['status'] in ('expired', 'cancelled'): tx1_order_status = tx1_order['status'] if order_match['backward_asset'] != config.BTC: util.credit(db, block_index, order_match['tx1_address'], order_match['backward_asset'], order_match['backward_quantity'], action='order {}'.format(tx1_order_status), event=order_match['id']) else: tx1_give_remaining = tx1_order['give_remaining'] + order_match['backward_quantity'] tx1_get_remaining = tx1_order['get_remaining'] + order_match['forward_quantity'] if tx1_order['get_asset'] == config.BTC and (block_index >= 297000 or config.TESTNET): # Protocol change. tx1_fee_required_remaining = tx1_order['fee_required_remaining'] + order_match['fee_paid'] else: tx1_fee_required_remaining = tx1_order['fee_required_remaining'] tx1_order_status = tx1_order['status'] bindings = { 'give_remaining': tx1_give_remaining, 'get_remaining': tx1_get_remaining, 'status': tx1_order_status, 'fee_required_remaining': tx1_fee_required_remaining, 'tx_hash': order_match['tx1_hash'] } sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) if block_index < 286500: # Protocol change. # Sanity check: one of the two must have expired. tx0_order_time_left = tx0_order['expire_index'] - block_index tx1_order_time_left = tx1_order['expire_index'] - block_index assert tx0_order_time_left or tx1_order_time_left # Penalize tardiness. if block_index >= 313900 or config.TESTNET: # Protocol change. if tx0_order['status'] == 'expired' and order_match['forward_asset'] == config.BTC: exact_penalty(db, order_match['tx0_address'], block_index, order_match['id']) if tx1_order['status'] == 'expired' and order_match['backward_asset'] == config.BTC: exact_penalty(db, order_match['tx1_address'], block_index, order_match['id']) # Re‐match. if block_index >= 310000 or config.TESTNET: # Protocol change. if not (block_index >= 315000 or config.TESTNET): # Protocol change. cursor.execute('''SELECT * FROM transactions\ WHERE tx_hash = ?''', (tx0_order['tx_hash'],)) match(db, list(cursor)[0], block_index) cursor.execute('''SELECT * FROM transactions\ WHERE tx_hash = ?''', (tx1_order['tx_hash'],)) match(db, list(cursor)[0], block_index) if status == 'expired': # Record order match expiration. bindings = { 'order_match_id': order_match['id'], 'tx0_address': order_match['tx0_address'], 'tx1_address': order_match['tx1_address'], 'block_index': block_index } sql='insert into order_match_expirations values(:order_match_id, :tx0_address, :tx1_address, :block_index)' cursor.execute(sql, bindings) cursor.close()
def parse(db, tx, message): cursor = db.cursor() # Unpack message. try: if len(message) - LENGTH <= 52: curr_format = FORMAT + '{}p'.format(len(message) - LENGTH) else: curr_format = FORMAT + '{}s'.format(len(message) - LENGTH) timestamp, value, fee_fraction_int, text = struct.unpack( curr_format, message) try: text = text.decode('utf-8') except UnicodeDecodeError: text = '' status = 'valid' except (struct.error) as e: timestamp, value, fee_fraction_int, text = 0, None, 0, None status = 'invalid: could not unpack' if status == 'valid': # For SQLite3 timestamp = min(timestamp, config.MAX_INT) value = min(value, config.MAX_INT) problems = validate(db, tx['source'], timestamp, value, fee_fraction_int, text, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) # Lock? lock = False if text and text.lower() == 'lock': lock = True timestamp, value, fee_fraction_int, text = 0, None, None, None else: lock = False # 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'], 'timestamp': timestamp, 'value': value, 'fee_fraction_int': fee_fraction_int, 'text': text, 'locked': lock, 'status': status, } sql = 'insert into broadcasts values(:tx_index, :tx_hash, :block_index, :source, :timestamp, :value, :fee_fraction_int, :text, :locked, :status)' cursor.execute(sql, bindings) # Negative values (default to ignore). if value == None or value < 0: # Cancel Open Bets? if value == -2: cursor.execute( '''SELECT * FROM bets \ WHERE (status = ? AND feed_address = ?)''', ('open', tx['source'])) for i in list(cursor): bet.cancel_bet(db, i, 'dropped', tx['block_index']) # Cancel Pending Bet Matches? if value == -3: cursor.execute( '''SELECT * FROM bet_matches \ WHERE (status = ? AND feed_address = ?)''', ('pending', tx['source'])) for bet_match in list(cursor): bet.cancel_bet_match(db, bet_match, 'dropped', tx['block_index']) cursor.close() return # Handle bet matches that use this feed. cursor.execute( '''SELECT * FROM bet_matches \ WHERE (status=? AND feed_address=?) ORDER BY tx1_index ASC, tx0_index ASC''', ('pending', tx['source'])) for bet_match in cursor.fetchall(): broadcast_bet_match_cursor = db.cursor() bet_match_id = util.make_id(bet_match['tx0_hash'], bet_match['tx1_hash']) bet_match_status = None # Calculate total funds held in escrow and total fee to be paid if # the bet match is settled. Escrow less fee is amount to be paid back # to betters. total_escrow = bet_match['forward_quantity'] + bet_match[ 'backward_quantity'] fee_fraction = fee_fraction_int / config.UNIT fee = int(fee_fraction * total_escrow) # Truncate. escrow_less_fee = total_escrow - fee # Get known bet match type IDs. cfd_type_id = util.BET_TYPE_ID['BullCFD'] + util.BET_TYPE_ID['BearCFD'] equal_type_id = util.BET_TYPE_ID['Equal'] + util.BET_TYPE_ID['NotEqual'] # Get the bet match type ID of this bet match. bet_match_type_id = bet_match['tx0_bet_type'] + bet_match[ 'tx1_bet_type'] # Contract for difference, with determinate settlement date. if bet_match_type_id == cfd_type_id: # Recognise tx0, tx1 as the bull, bear (in the right direction). if bet_match['tx0_bet_type'] < bet_match['tx1_bet_type']: bull_address = bet_match['tx0_address'] bear_address = bet_match['tx1_address'] bull_escrow = bet_match['forward_quantity'] bear_escrow = bet_match['backward_quantity'] else: bull_address = bet_match['tx1_address'] bear_address = bet_match['tx0_address'] bull_escrow = bet_match['backward_quantity'] bear_escrow = bet_match['forward_quantity'] leverage = Fraction(bet_match['leverage'], 5040) initial_value = bet_match['initial_value'] bear_credit = bear_escrow - ( value - initial_value) * leverage * config.UNIT bull_credit = escrow_less_fee - bear_credit bear_credit = round(bear_credit) bull_credit = round(bull_credit) # Liquidate, as necessary. if bull_credit >= escrow_less_fee or bull_credit <= 0: if bull_credit >= escrow_less_fee: bull_credit = escrow_less_fee bear_credit = 0 bet_match_status = 'settled: liquidated for bull' util.credit(db, tx['block_index'], bull_address, config.XCP, bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) elif bull_credit <= 0: bull_credit = 0 bear_credit = escrow_less_fee bet_match_status = 'settled: liquidated for bear' util.credit(db, tx['block_index'], bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, tx['block_index'], bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': False, 'bull_credit': bull_credit, 'bear_credit': bear_credit, 'winner': None, 'escrow_less_fee': None, 'fee': fee } sql = 'insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Settle (if not liquidated). elif timestamp >= bet_match['deadline']: bet_match_status = 'settled' util.credit(db, tx['block_index'], bull_address, config.XCP, bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) util.credit(db, tx['block_index'], bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, tx['block_index'], bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': True, 'bull_credit': bull_credit, 'bear_credit': bear_credit, 'winner': None, 'escrow_less_fee': None, 'fee': fee } sql = 'insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Equal[/NotEqual] bet. elif bet_match_type_id == equal_type_id and timestamp >= bet_match[ 'deadline']: # Recognise tx0, tx1 as the bull, bear (in the right direction). if bet_match['tx0_bet_type'] < bet_match['tx1_bet_type']: equal_address = bet_match['tx0_address'] notequal_address = bet_match['tx1_address'] else: equal_address = bet_match['tx1_address'] notequal_address = bet_match['tx0_address'] # Decide who won, and credit appropriately. if value == bet_match['target_value']: winner = 'Equal' bet_match_status = 'settled: for equal' util.credit(db, tx['block_index'], equal_address, config.XCP, escrow_less_fee, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) else: winner = 'NotEqual' bet_match_status = 'settled: for notequal' util.credit(db, tx['block_index'], notequal_address, config.XCP, escrow_less_fee, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, tx['block_index'], bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': None, 'bull_credit': None, 'bear_credit': None, 'winner': winner, 'escrow_less_fee': escrow_less_fee, 'fee': fee } sql = 'insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Update the bet match’s status. if bet_match_status: bindings = { 'status': bet_match_status, 'bet_match_id': util.make_id(bet_match['tx0_hash'], bet_match['tx1_hash']) } sql = 'update bet_matches set status = :status where id = :bet_match_id' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'bet_matches', bindings) broadcast_bet_match_cursor.close() cursor.close()
def parse (db, tx, message): cursor = db.cursor() # Unpack message. try: if len(message) - LENGTH <= 52: curr_format = FORMAT + '{}p'.format(len(message) - LENGTH) else: curr_format = FORMAT + '{}s'.format(len(message) - LENGTH) timestamp, value, fee_fraction_int, text = struct.unpack(curr_format, message) try: text = text.decode('utf-8') except UnicodeDecodeError: text = '' status = 'valid' except (struct.error) as e: timestamp, value, fee_fraction_int, text = 0, None, 0, None status = 'invalid: could not unpack' if status == 'valid': # For SQLite3 timestamp = min(timestamp, config.MAX_INT) value = min(value, config.MAX_INT) problems = validate(db, tx['source'], timestamp, value, fee_fraction_int, text, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) # Lock? lock = False if text and text.lower() == 'lock': lock = True timestamp, value, fee_fraction_int, text = 0, None, None, None else: lock = False # 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'], 'timestamp': timestamp, 'value': value, 'fee_fraction_int': fee_fraction_int, 'text': text, 'locked': lock, 'status': status, } sql='insert into broadcasts values(:tx_index, :tx_hash, :block_index, :source, :timestamp, :value, :fee_fraction_int, :text, :locked, :status)' cursor.execute(sql, bindings) # Negative values (default to ignore). if value == None or value < 0: # Cancel Open Bets? if value == -2: cursor.execute('''SELECT * FROM bets \ WHERE (status = ? AND feed_address = ?)''', ('open', tx['source'])) for i in list(cursor): bet.cancel_bet(db, i, 'dropped', tx['block_index']) # Cancel Pending Bet Matches? if value == -3: cursor.execute('''SELECT * FROM bet_matches \ WHERE (status = ? AND feed_address = ?)''', ('pending', tx['source'])) for bet_match in list(cursor): bet.cancel_bet_match(db, bet_match, 'dropped', tx['block_index']) cursor.close() return # Handle bet matches that use this feed. cursor.execute('''SELECT * FROM bet_matches \ WHERE (status=? AND feed_address=?) ORDER BY tx1_index ASC, tx0_index ASC''', ('pending', tx['source'])) for bet_match in cursor.fetchall(): broadcast_bet_match_cursor = db.cursor() bet_match_id = util.make_id(bet_match['tx0_hash'], bet_match['tx1_hash']) bet_match_status = None # Calculate total funds held in escrow and total fee to be paid if # the bet match is settled. Escrow less fee is amount to be paid back # to betters. total_escrow = bet_match['forward_quantity'] + bet_match['backward_quantity'] fee_fraction = fee_fraction_int / config.UNIT fee = int(fee_fraction * total_escrow) # Truncate. escrow_less_fee = total_escrow - fee # Get known bet match type IDs. cfd_type_id = util.BET_TYPE_ID['BullCFD'] + util.BET_TYPE_ID['BearCFD'] equal_type_id = util.BET_TYPE_ID['Equal'] + util.BET_TYPE_ID['NotEqual'] # Get the bet match type ID of this bet match. bet_match_type_id = bet_match['tx0_bet_type'] + bet_match['tx1_bet_type'] # Contract for difference, with determinate settlement date. if bet_match_type_id == cfd_type_id: # Recognise tx0, tx1 as the bull, bear (in the right direction). if bet_match['tx0_bet_type'] < bet_match['tx1_bet_type']: bull_address = bet_match['tx0_address'] bear_address = bet_match['tx1_address'] bull_escrow = bet_match['forward_quantity'] bear_escrow = bet_match['backward_quantity'] else: bull_address = bet_match['tx1_address'] bear_address = bet_match['tx0_address'] bull_escrow = bet_match['backward_quantity'] bear_escrow = bet_match['forward_quantity'] leverage = Fraction(bet_match['leverage'], 5040) initial_value = bet_match['initial_value'] bear_credit = bear_escrow - (value - initial_value) * leverage * config.UNIT bull_credit = escrow_less_fee - bear_credit bear_credit = round(bear_credit) bull_credit = round(bull_credit) # Liquidate, as necessary. if bull_credit >= escrow_less_fee or bull_credit <= 0: if bull_credit >= escrow_less_fee: bull_credit = escrow_less_fee bear_credit = 0 bet_match_status = 'settled: liquidated for bull' util.credit(db, tx['block_index'], bull_address, config.XCP, bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) elif bull_credit <= 0: bull_credit = 0 bear_credit = escrow_less_fee bet_match_status = 'settled: liquidated for bear' util.credit(db, tx['block_index'], bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, tx['block_index'], bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': False, 'bull_credit': bull_credit, 'bear_credit': bear_credit, 'winner': None, 'escrow_less_fee': None, 'fee': fee } sql='insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Settle (if not liquidated). elif timestamp >= bet_match['deadline']: bet_match_status = 'settled' util.credit(db, tx['block_index'], bull_address, config.XCP, bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) util.credit(db, tx['block_index'], bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, tx['block_index'], bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': True, 'bull_credit': bull_credit, 'bear_credit': bear_credit, 'winner': None, 'escrow_less_fee': None, 'fee': fee } sql='insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Equal[/NotEqual] bet. elif bet_match_type_id == equal_type_id and timestamp >= bet_match['deadline']: # Recognise tx0, tx1 as the bull, bear (in the right direction). if bet_match['tx0_bet_type'] < bet_match['tx1_bet_type']: equal_address = bet_match['tx0_address'] notequal_address = bet_match['tx1_address'] else: equal_address = bet_match['tx1_address'] notequal_address = bet_match['tx0_address'] # Decide who won, and credit appropriately. if value == bet_match['target_value']: winner = 'Equal' bet_match_status = 'settled: for equal' util.credit(db, tx['block_index'], equal_address, config.XCP, escrow_less_fee, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) else: winner = 'NotEqual' bet_match_status = 'settled: for notequal' util.credit(db, tx['block_index'], notequal_address, config.XCP, escrow_less_fee, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, tx['block_index'], bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': None, 'bull_credit': None, 'bear_credit': None, 'winner': winner, 'escrow_less_fee': escrow_less_fee, 'fee': fee } sql='insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Update the bet match’s status. if bet_match_status: bindings = { 'status': bet_match_status, 'bet_match_id': util.make_id(bet_match['tx0_hash'], bet_match['tx1_hash']) } sql='update bet_matches set status = :status where id = :bet_match_id' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'bet_matches', bindings) broadcast_bet_match_cursor.close() cursor.close()
def match (db, tx, block_index=None): cursor = db.cursor() # Get order in question. orders = list(cursor.execute('''SELECT * FROM orders\ WHERE (tx_index = ? AND status = ?)''', (tx['tx_index'], 'open'))) if not orders: cursor.close() return else: assert len(orders) == 1 tx1 = orders[0] cursor.execute('''SELECT * FROM orders \ WHERE (give_asset=? AND get_asset=? AND status=? AND tx_hash != ?)''', (tx1['get_asset'], tx1['give_asset'], 'open', tx1['tx_hash'])) tx1_give_remaining = tx1['give_remaining'] tx1_get_remaining = tx1['get_remaining'] order_matches = cursor.fetchall() if tx['block_index'] > 284500 or config.TESTNET: # Protocol change. order_matches = sorted(order_matches, key=lambda x: x['tx_index']) # Sort by tx index second. order_matches = sorted(order_matches, key=lambda x: util.price(x['get_quantity'], x['give_quantity'], tx1['block_index'])) # Sort by price first. # Get fee remaining. tx1_fee_required_remaining = tx1['fee_required_remaining'] tx1_fee_provided_remaining = tx1['fee_provided_remaining'] tx1_status = tx1['status'] for tx0 in order_matches: order_match_id = util.make_id(tx0['tx_hash'], tx1['tx_hash']) if not block_index: block_index = max(tx0['block_index'], tx1['block_index']) if tx1_status != 'open': break logger.debug('Considering: ' + tx0['tx_hash']) tx0_give_remaining = tx0['give_remaining'] tx0_get_remaining = tx0['get_remaining'] # Ignore previous matches. (Both directions, just to be sure.) cursor.execute('''SELECT * FROM order_matches WHERE id = ? ''', (util.make_id(tx0['tx_hash'], tx1['tx_hash']), )) if list(cursor): logger.debug('Skipping: previous match') continue cursor.execute('''SELECT * FROM order_matches WHERE id = ? ''', (util.make_id(tx1['tx_hash'], tx0['tx_hash']), )) if list(cursor): logger.debug('Skipping: previous match') continue # Get fee provided remaining. tx0_fee_required_remaining = tx0['fee_required_remaining'] tx0_fee_provided_remaining = tx0['fee_provided_remaining'] # Make sure that that both orders still have funds remaining (if order involves BTC, and so cannot be ‘filled’). if tx0['give_asset'] == config.BTC or tx0['get_asset'] == config.BTC: # Gratuitous if tx0_give_remaining <= 0 or tx1_give_remaining <= 0: logger.debug('Skipping: negative give quantity remaining') continue if block_index >= 292000 and block_index <= 310500 and not config.TESTNET: # Protocol changes if tx0_get_remaining <= 0 or tx1_get_remaining <= 0: logger.debug('Skipping: negative get quantity remaining') continue if block_index >= 294000 or config.TESTNET: # Protocol change. if tx0['fee_required_remaining'] < 0: logger.debug('Skipping: negative tx0 fee required remaining') continue if tx0['fee_provided_remaining'] < 0: logger.debug('Skipping: negative tx0 fee provided remaining') continue if tx1_fee_provided_remaining < 0: logger.debug('Skipping: negative tx1 fee provided remaining') continue if tx1_fee_required_remaining < 0: logger.debug('Skipping: negative tx1 fee required remaining') continue # If the prices agree, make the trade. The found order sets the price, # and they trade as much as they can. tx0_price = util.price(tx0['get_quantity'], tx0['give_quantity'], block_index) tx1_price = util.price(tx1['get_quantity'], tx1['give_quantity'], block_index) tx1_inverse_price = util.price(tx1['give_quantity'], tx1['get_quantity'], block_index) # Protocol change. if tx['block_index'] < 286000: tx1_inverse_price = util.price(1, tx1_price, block_index) logger.debug('Tx0 Price: {}; Tx1 Inverse Price: {}'.format(float(tx0_price), float(tx1_inverse_price))) if tx0_price > tx1_inverse_price: logger.debug('Skipping: price mismatch.') else: logger.debug('Potential forward quantities: {}, {}'.format(tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price, block_index)))) forward_quantity = int(min(tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price, block_index)))) logger.debug('Forward Quantity: {}'.format(forward_quantity)) backward_quantity = round(forward_quantity * tx0_price) logger.debug('Backward Quantity: {}'.format(backward_quantity)) if not forward_quantity: logger.debug('Skipping: zero forward quantity.') continue if block_index >= 286500 or config.TESTNET: # Protocol change. if not backward_quantity: logger.debug('Skipping: zero backward quantity.') continue forward_asset, backward_asset = tx1['get_asset'], tx1['give_asset'] if block_index >= 313900 or config.TESTNET: # Protocol change. min_btc_quantity = 0.001 * config.UNIT # 0.001 BTC if (forward_asset == config.BTC and forward_quantity <= min_btc_quantity) or (backward_asset == config.BTC and backward_quantity <= min_btc_quantity): logger.debug('Skipping: below minimum {} quantity'.format(config.BTC)) continue # Check and update fee remainings. fee = 0 if block_index >= 286500 or config.TESTNET: # Protocol change. Deduct fee_required from provided_remaining, etc., if possible (else don’t match). if tx1['get_asset'] == config.BTC: if block_index >= 310500 or config.TESTNET: # Protocol change. fee = int(tx1['fee_required'] * util.price(backward_quantity, tx1['give_quantity'], block_index)) else: fee = int(tx1['fee_required_remaining'] * util.price(forward_quantity, tx1_get_remaining, block_index)) logger.debug('Tx0 fee provided remaining: {}; required fee: {}'.format(tx0_fee_provided_remaining / config.UNIT, fee / config.UNIT)) if tx0_fee_provided_remaining < fee: logger.debug('Skipping: tx0 fee provided remaining is too low.') continue else: tx0_fee_provided_remaining -= fee if block_index >= 287800 or config.TESTNET: # Protocol change. tx1_fee_required_remaining -= fee elif tx1['give_asset'] == config.BTC: if block_index >= 310500 or config.TESTNET: # Protocol change. fee = int(tx0['fee_required'] * util.price(backward_quantity, tx0['give_quantity'], block_index)) else: fee = int(tx0['fee_required_remaining'] * util.price(backward_quantity, tx0_get_remaining, block_index)) logger.debug('Tx1 fee provided remaining: {}; required fee: {}'.format(tx1_fee_provided_remaining / config.UNIT, fee / config.UNIT)) if tx1_fee_provided_remaining < fee: logger.debug('Skipping: tx1 fee provided remaining is too low.') continue else: tx1_fee_provided_remaining -= fee if block_index >= 287800 or config.TESTNET: # Protocol change. tx0_fee_required_remaining -= fee else: # Don’t deduct. if tx1['get_asset'] == config.BTC: if tx0_fee_provided_remaining < tx1['fee_required']: continue elif tx1['give_asset'] == config.BTC: if tx1_fee_provided_remaining < tx0['fee_required']: continue if config.BTC in (tx1['give_asset'], tx1['get_asset']): status = 'pending' else: status = 'completed' # Credit. util.credit(db, tx['block_index'], tx1['source'], tx1['get_asset'], forward_quantity, action='order match', event=order_match_id) util.credit(db, tx['block_index'], tx0['source'], tx0['get_asset'], backward_quantity, action='order match', event=order_match_id) # Debit the order, even if it involves giving bitcoins, and so one # can't debit the sending account. # Get remainings may be negative. tx0_give_remaining -= forward_quantity tx0_get_remaining -= backward_quantity tx1_give_remaining -= backward_quantity tx1_get_remaining -= forward_quantity # Update give_remaining, get_remaining. # tx0 tx0_status = 'open' if tx0_give_remaining <= 0 or (tx0_get_remaining <= 0 and (block_index >= 292000 or config.TESTNET)): # Protocol change if tx0['give_asset'] != config.BTC and tx0['get_asset'] != config.BTC: # Fill order, and recredit give_remaining. tx0_status = 'filled' util.credit(db, block_index, tx0['source'], tx0['give_asset'], tx0_give_remaining, event=tx1['tx_hash'], action='filled') bindings = { 'give_remaining': tx0_give_remaining, 'get_remaining': tx0_get_remaining, 'fee_required_remaining': tx0_fee_required_remaining, 'fee_provided_remaining': tx0_fee_provided_remaining, 'status': tx0_status, 'tx_hash': tx0['tx_hash'] } sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) # tx1 if tx1_give_remaining <= 0 or (tx1_get_remaining <= 0 and (block_index >= 292000 or config.TESTNET)): # Protocol change if tx1['give_asset'] != config.BTC and tx1['get_asset'] != config.BTC: # Fill order, and recredit give_remaining. tx1_status = 'filled' util.credit(db, block_index, tx1['source'], tx1['give_asset'], tx1_give_remaining, event=tx0['tx_hash'], action='filled') bindings = { 'give_remaining': tx1_give_remaining, 'get_remaining': tx1_get_remaining, 'fee_required_remaining': tx1_fee_required_remaining, 'fee_provided_remaining': tx1_fee_provided_remaining, 'status': tx1_status, 'tx_hash': tx1['tx_hash'] } sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) # Calculate when the match will expire. if block_index >= 308000 or config.TESTNET: # Protocol change. match_expire_index = block_index + 20 elif block_index >= 286500 or config.TESTNET: # Protocol change. match_expire_index = block_index + 10 else: match_expire_index = min(tx0['expire_index'], tx1['expire_index']) # Record order match. bindings = { 'id': util.make_id(tx0['tx_hash'], tx['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_hash'], 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'forward_asset': forward_asset, 'forward_quantity': forward_quantity, 'backward_asset': backward_asset, 'backward_quantity': backward_quantity, 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': block_index, 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': match_expire_index, 'fee_paid': fee, 'status': status, } sql='insert into order_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :forward_asset, :forward_quantity, :backward_asset, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_paid, :status)' cursor.execute(sql, bindings) if tx1_status == 'filled': break cursor.close() return
def follow(db, proxy): cursor = db.cursor() # Initialise. initialise(db) # Get index of last block. try: block_index = util.last_block(db)['block_index'] + 1 # Reparse all transactions if minor version has changed. minor_version = cursor.execute('PRAGMA user_version').fetchall()[0]['user_version'] if minor_version != config.VERSION_MINOR: logger.info('Client minor version number mismatch ({} ≠ {}).'.format(minor_version, config.VERSION_MINOR)) reparse(db, quiet=False) logger.info('Resuming parsing.') except exceptions.DatabaseError: logger.warning('New database.') block_index = config.BLOCK_FIRST # Get index of last transaction. tx_index = get_next_tx_index(db) not_supported = {} # No false positives. Use a dict to allow for O(1) lookups not_supported_sorted = collections.deque() # ^ Entries in form of (block_index, tx_hash), oldest first. Allows for easy removal of past, unncessary entries mempool_initialised = False # a reorg can happen without the block count increasing, or even for that # matter, with the block count decreasing. This should only delay # processing of the new blocks a bit. while True: starttime = time.time() # Get block count. # If the backend is unreachable and `config.FORCE` is set, just sleep # and try again repeatedly. try: block_count = backend.getblockcount(proxy) except (ConnectionRefusedError, http.client.CannotSendRequest) as e: if config.FORCE: time.sleep(config.BACKEND_POLL_INTERVAL) continue else: raise e # Get new blocks. if block_index <= block_count: # Backwards check for incorrect blocks due to chain reorganisation, and stop when a common parent is found. current_index = block_index requires_rollback = False while True: if current_index == config.BLOCK_FIRST: break logger.debug('Checking that block {} is not an orphan.'.format(current_index)) # Backend parent hash. current_hash_bin = backend.getblockhash(proxy, current_index) current_cblock = backend.getblock(proxy, current_hash_bin) backend_parent = bitcoinlib.core.b2lx(current_cblock.hashPrevBlock) # DB parent hash. blocks = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (current_index - 1,))) if len(blocks) != 1: # For empty DB. break db_parent = blocks[0]['block_hash'] # Compare. assert type(db_parent) == str assert type(backend_parent) == str if db_parent == backend_parent: break else: current_index -= 1 requires_rollback = True # Rollback for reorganisation. if requires_rollback: # Record reorganisation. logger.warning('Blockchain reorganisation at block {}.'.format(current_index)) log.message(db, block_index, 'reorg', None, {'block_index': current_index}) # Rollback the DB. reparse(db, block_index=current_index-1, quiet=True) block_index = current_index tx_index = get_next_tx_index(db) continue # Get and parse transactions in this block (atomically). block_hash_bin = backend.getblockhash(proxy, current_index) block = backend.getblock(proxy, block_hash_bin) block_hash = bitcoinlib.core.b2lx(block_hash_bin) previous_block_hash = bitcoinlib.core.b2lx(block.hashPrevBlock) block_time = block.nTime txhash_list = backend.get_txhash_list(block) with db: # List the block. cursor.execute('''INSERT INTO blocks( block_index, block_hash, block_time, previous_block_hash, difficulty) VALUES(?,?,?,?,?)''', (block_index, block_hash, block_time, previous_block_hash, block.difficulty) ) # List the transactions in the block. for tx_hash in txhash_list: # TODO: use rpc._batch to get all transactions with one RPC call tx_index = list_tx(db, proxy, block_hash, block_index, block_time, tx_hash, tx_index) # Parse the transactions in the block. parse_block(db, block_index, block_time) # When newly caught up, check for conservation of assets. if block_index == block_count: check.asset_conservation(db) # Remove any non‐supported transactions older than ten blocks. while len(not_supported_sorted) and not_supported_sorted[0][0] <= block_index - 10: tx_h = not_supported_sorted.popleft()[1] del not_supported[tx_h] logger.info('Block: %s (%ss)'%(str(block_index), "{:.2f}".format(time.time() - starttime, 3))) # Increment block index. block_count = backend.getblockcount(proxy) block_index += 1 else: # First mempool fill for session? if mempool_initialised: logger.debug('Updating mempool.') else: logger.debug('Initialising mempool.') # Get old counterpartyd mempool. old_mempool = list(cursor.execute('''SELECT * FROM mempool''')) old_mempool_hashes = [message['tx_hash'] for message in old_mempool] # Fake values for fake block. curr_time = int(time.time()) mempool_tx_index = tx_index # For each transaction in Bitcoin Core mempool, if it’s new, create # a fake block, a fake transaction, capture the generated messages, # and then save those messages. # Every transaction in mempool is parsed independently. (DB is rolled back after each one.) mempool = [] util.MEMPOOL = backend.getrawmempool(proxy) for tx_hash in util.MEMPOOL: tx_hash = bitcoinlib.core.b2lx(tx_hash) # If already in counterpartyd mempool, copy to new one. if tx_hash in old_mempool_hashes: for message in old_mempool: if message['tx_hash'] == tx_hash: mempool.append((tx_hash, message)) # If already skipped, skip it again. elif tx_hash not in not_supported: # Else: list, parse and save it. try: with db: # List the fake block. cursor.execute('''INSERT INTO blocks( block_index, block_hash, block_time) VALUES(?,?,?)''', (config.MEMPOOL_BLOCK_INDEX, config.MEMPOOL_BLOCK_HASH, curr_time) ) # List transaction. try: # Sometimes the transactions can’t be found: `{'code': -5, 'message': 'No information available about transaction'} Is txindex enabled in Bitcoind?` mempool_tx_index = list_tx(db, proxy, None, block_index, curr_time, tx_hash, mempool_tx_index) except backend.BitcoindError: raise MempoolError # Parse transaction. cursor.execute('''SELECT * FROM transactions \ WHERE tx_hash = ?''', (tx_hash,)) transactions = list(cursor) if transactions: assert len(transactions) == 1 transaction = transactions[0] supported = parse_tx(db, transaction) if not supported: not_supported[tx_hash] = '' not_supported_sorted.append((block_index, tx_hash)) else: # If a transaction hasn’t been added to the # table `transactions`, then it’s not a # Counterparty transaction. not_supported[tx_hash] = '' not_supported_sorted.append((block_index, tx_hash)) raise MempoolError # Save transaction and side‐effects in memory. cursor.execute('''SELECT * FROM messages WHERE block_index = ?''', (config.MEMPOOL_BLOCK_INDEX,)) for message in list(cursor): mempool.append((tx_hash, message)) # Rollback. raise MempoolError except MempoolError: pass # Re‐write mempool messages to database. with db: cursor.execute('''DELETE FROM mempool''') for message in mempool: tx_hash, new_message = message new_message['tx_hash'] = tx_hash cursor.execute('''INSERT INTO mempool VALUES(:tx_hash, :command, :category, :bindings, :timestamp)''', (new_message)) # Wait mempool_initialised = True db.wal_checkpoint(mode=apsw.SQLITE_CHECKPOINT_PASSIVE) time.sleep(config.BACKEND_POLL_INTERVAL) cursor.close()
def follow(db, proxy): cursor = db.cursor() # Initialise. initialise(db) # Get index of last block. try: block_index = util.last_block(db)['block_index'] + 1 # Reparse all transactions if minor version has changed. minor_version = cursor.execute( 'PRAGMA user_version').fetchall()[0]['user_version'] if minor_version != config.VERSION_MINOR: logger.info( 'Client minor version number mismatch ({} ≠ {}).'.format( minor_version, config.VERSION_MINOR)) reparse(db, quiet=False) logger.info('Resuming parsing.') except exceptions.DatabaseError: logger.warning('New database.') block_index = config.BLOCK_FIRST # Get index of last transaction. tx_index = get_next_tx_index(db) not_supported = { } # No false positives. Use a dict to allow for O(1) lookups not_supported_sorted = collections.deque() # ^ Entries in form of (block_index, tx_hash), oldest first. Allows for easy removal of past, unncessary entries mempool_initialised = False # a reorg can happen without the block count increasing, or even for that # matter, with the block count decreasing. This should only delay # processing of the new blocks a bit. while True: starttime = time.time() # Get block count. # If the backend is unreachable and `config.FORCE` is set, just sleep # and try again repeatedly. try: block_count = backend.getblockcount(proxy) except (ConnectionRefusedError, http.client.CannotSendRequest) as e: if config.FORCE: time.sleep(config.BACKEND_POLL_INTERVAL) continue else: raise e # Get new blocks. if block_index <= block_count: # Backwards check for incorrect blocks due to chain reorganisation, and stop when a common parent is found. current_index = block_index requires_rollback = False while True: if current_index == config.BLOCK_FIRST: break logger.debug('Checking that block {} is not an orphan.'.format( current_index)) # Backend parent hash. current_hash_bin = backend.getblockhash(proxy, current_index) current_cblock = backend.getblock(proxy, current_hash_bin) backend_parent = bitcoinlib.core.b2lx( current_cblock.hashPrevBlock) # DB parent hash. blocks = list( cursor.execute( '''SELECT * FROM blocks WHERE block_index = ?''', (current_index - 1, ))) if len(blocks) != 1: # For empty DB. break db_parent = blocks[0]['block_hash'] # Compare. assert type(db_parent) == str assert type(backend_parent) == str if db_parent == backend_parent: break else: current_index -= 1 requires_rollback = True # Rollback for reorganisation. if requires_rollback: # Record reorganisation. logger.warning('Blockchain reorganisation at block {}.'.format( current_index)) log.message(db, block_index, 'reorg', None, {'block_index': current_index}) # Rollback the DB. reparse(db, block_index=current_index - 1, quiet=True) block_index = current_index tx_index = get_next_tx_index(db) continue # Get and parse transactions in this block (atomically). block_hash_bin = backend.getblockhash(proxy, current_index) block = backend.getblock(proxy, block_hash_bin) block_hash = bitcoinlib.core.b2lx(block_hash_bin) previous_block_hash = bitcoinlib.core.b2lx(block.hashPrevBlock) block_time = block.nTime txhash_list = backend.get_txhash_list(block) with db: # List the block. cursor.execute( '''INSERT INTO blocks( block_index, block_hash, block_time, previous_block_hash, difficulty) VALUES(?,?,?,?,?)''', (block_index, block_hash, block_time, previous_block_hash, block.difficulty)) # List the transactions in the block. for tx_hash in txhash_list: # TODO: use rpc._batch to get all transactions with one RPC call tx_index = list_tx(db, proxy, block_hash, block_index, block_time, tx_hash, tx_index) # Parse the transactions in the block. parse_block(db, block_index, block_time) # When newly caught up, check for conservation of assets. if block_index == block_count: check.asset_conservation(db) # Remove any non‐supported transactions older than ten blocks. while len(not_supported_sorted ) and not_supported_sorted[0][0] <= block_index - 10: tx_h = not_supported_sorted.popleft()[1] del not_supported[tx_h] logger.info('Block: %s (%ss)' % (str(block_index), "{:.2f}".format( time.time() - starttime, 3))) # Increment block index. block_count = backend.getblockcount(proxy) block_index += 1 else: # First mempool fill for session? if mempool_initialised: logger.debug('Updating mempool.') else: logger.debug('Initialising mempool.') # Get old counterpartyd mempool. old_mempool = list(cursor.execute('''SELECT * FROM mempool''')) old_mempool_hashes = [ message['tx_hash'] for message in old_mempool ] # Fake values for fake block. curr_time = int(time.time()) mempool_tx_index = tx_index # For each transaction in Bitcoin Core mempool, if it’s new, create # a fake block, a fake transaction, capture the generated messages, # and then save those messages. # Every transaction in mempool is parsed independently. (DB is rolled back after each one.) mempool = [] util.MEMPOOL = backend.getrawmempool(proxy) for tx_hash in util.MEMPOOL: tx_hash = bitcoinlib.core.b2lx(tx_hash) # If already in counterpartyd mempool, copy to new one. if tx_hash in old_mempool_hashes: for message in old_mempool: if message['tx_hash'] == tx_hash: mempool.append((tx_hash, message)) # If already skipped, skip it again. elif tx_hash not in not_supported: # Else: list, parse and save it. try: with db: # List the fake block. cursor.execute( '''INSERT INTO blocks( block_index, block_hash, block_time) VALUES(?,?,?)''', (config.MEMPOOL_BLOCK_INDEX, config.MEMPOOL_BLOCK_HASH, curr_time)) # List transaction. try: # Sometimes the transactions can’t be found: `{'code': -5, 'message': 'No information available about transaction'} Is txindex enabled in Bitcoind?` mempool_tx_index = list_tx( db, proxy, None, block_index, curr_time, tx_hash, mempool_tx_index) except backend.BitcoindError: raise MempoolError # Parse transaction. cursor.execute( '''SELECT * FROM transactions \ WHERE tx_hash = ?''', (tx_hash, )) transactions = list(cursor) if transactions: assert len(transactions) == 1 transaction = transactions[0] supported = parse_tx(db, transaction) if not supported: not_supported[tx_hash] = '' not_supported_sorted.append( (block_index, tx_hash)) else: # If a transaction hasn’t been added to the # table `transactions`, then it’s not a # Counterparty transaction. not_supported[tx_hash] = '' not_supported_sorted.append( (block_index, tx_hash)) raise MempoolError # Save transaction and side‐effects in memory. cursor.execute( '''SELECT * FROM messages WHERE block_index = ?''', (config.MEMPOOL_BLOCK_INDEX, )) for message in list(cursor): mempool.append((tx_hash, message)) # Rollback. raise MempoolError except MempoolError: pass # Re‐write mempool messages to database. with db: cursor.execute('''DELETE FROM mempool''') for message in mempool: tx_hash, new_message = message new_message['tx_hash'] = tx_hash cursor.execute( '''INSERT INTO mempool VALUES(:tx_hash, :command, :category, :bindings, :timestamp)''', (new_message)) # Wait mempool_initialised = True db.wal_checkpoint(mode=apsw.SQLITE_CHECKPOINT_PASSIVE) time.sleep(config.BACKEND_POLL_INTERVAL) cursor.close()
def match (db, tx): cursor = db.cursor() # Get bet in question. bets = list(cursor.execute('''SELECT * FROM bets\ WHERE (tx_index = ? AND status = ?)''', (tx['tx_index'], 'open'))) if not bets: cursor.close() return else: assert len(bets) == 1 tx1 = bets[0] # Get counterbet_type. if tx1['bet_type'] % 2: counterbet_type = tx1['bet_type'] - 1 else: counterbet_type = tx1['bet_type'] + 1 feed_address = tx1['feed_address'] cursor.execute('''SELECT * FROM bets\ WHERE (feed_address=? AND status=? AND bet_type=?)''', (tx1['feed_address'], 'open', counterbet_type)) tx1_wager_remaining = tx1['wager_remaining'] tx1_counterwager_remaining = tx1['counterwager_remaining'] bet_matches = cursor.fetchall() if tx['block_index'] > 284500 or config.TESTNET: # Protocol change. sorted(bet_matches, key=lambda x: x['tx_index']) # Sort by tx index second. sorted(bet_matches, key=lambda x: util.price(x['wager_quantity'], x['counterwager_quantity'], tx1['block_index'])) # Sort by price first. tx1_status = tx1['status'] for tx0 in bet_matches: if tx1_status != 'open': break logger.debug('Considering: ' + tx0['tx_hash']) tx0_wager_remaining = tx0['wager_remaining'] tx0_counterwager_remaining = tx0['counterwager_remaining'] # Bet types must be opposite. if counterbet_type != tx0['bet_type']: logger.debug('Skipping: bet types disagree.') continue # Leverages must agree exactly if tx0['leverage'] != tx1['leverage']: logger.debug('Skipping: leverages disagree.') continue # Target values must agree exactly. if tx0['target_value'] != tx1['target_value']: logger.debug('Skipping: target values disagree.') continue # Fee fractions must agree exactly. if tx0['fee_fraction_int'] != tx1['fee_fraction_int']: logger.debug('Skipping: fee fractions disagree.') continue # Deadlines must agree exactly. if tx0['deadline'] != tx1['deadline']: logger.debug('Skipping: deadlines disagree.') continue # If the odds agree, make the trade. The found order sets the odds, # and they trade as much as they can. tx0_odds = util.price(tx0['wager_quantity'], tx0['counterwager_quantity'], tx1['block_index']) tx0_inverse_odds = util.price(tx0['counterwager_quantity'], tx0['wager_quantity'], tx1['block_index']) tx1_odds = util.price(tx1['wager_quantity'], tx1['counterwager_quantity'], tx1['block_index']) if tx['block_index'] < 286000: tx0_inverse_odds = util.price(1, tx0_odds, tx1['block_index']) # Protocol change. logger.debug('Tx0 Inverse Odds: {}; Tx1 Odds: {}'.format(float(tx0_inverse_odds), float(tx1_odds))) if tx0_inverse_odds > tx1_odds: logger.debug('Skipping: price mismatch.') else: logger.debug('Potential forward quantities: {}, {}'.format(tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds, tx1['block_index'])))) forward_quantity = int(min(tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds, tx1['block_index'])))) logger.debug('Forward Quantity: {}'.format(forward_quantity)) backward_quantity = round(forward_quantity / tx0_odds) logger.debug('Backward Quantity: {}'.format(backward_quantity)) if not forward_quantity: logger.debug('Skipping: zero forward quantity.') continue if tx1['block_index'] >= 286500 or config.TESTNET: # Protocol change. if not backward_quantity: logger.debug('Skipping: zero backward quantity.') continue bet_match_id = util.make_id(tx0['tx_hash'], tx1['tx_hash']) # Debit the order. # Counterwager remainings may be negative. tx0_wager_remaining = tx0_wager_remaining - forward_quantity tx0_counterwager_remaining = tx0_counterwager_remaining - backward_quantity tx1_wager_remaining = tx1_wager_remaining - backward_quantity tx1_counterwager_remaining = tx1_counterwager_remaining - forward_quantity # tx0 tx0_status = 'open' if tx0_wager_remaining <= 0 or tx0_counterwager_remaining <= 0: # Fill order, and recredit give_remaining. tx0_status = 'filled' util.credit(db, tx1['block_index'], tx0['source'], config.XCP, tx0_wager_remaining, event=tx1['tx_hash'], action='filled') bindings = { 'wager_remaining': tx0_wager_remaining, 'counterwager_remaining': tx0_counterwager_remaining, 'status': tx0_status, 'tx_hash': tx0['tx_hash'] } sql='update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, tx1['block_index'], 'update', 'bets', bindings) if tx1['block_index'] >= 292000 or config.TESTNET: # Protocol change if tx1_wager_remaining <= 0 or tx1_counterwager_remaining <= 0: # Fill order, and recredit give_remaining. tx1_status = 'filled' util.credit(db, tx1['block_index'], tx1['source'], config.XCP, tx1_wager_remaining, event=tx1['tx_hash'], action='filled') # tx1 bindings = { 'wager_remaining': tx1_wager_remaining, 'counterwager_remaining': tx1_counterwager_remaining, 'status': tx1_status, 'tx_hash': tx1['tx_hash'] } sql='update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, tx1['block_index'], 'update', 'bets', bindings) # Get last value of feed. broadcasts = list(cursor.execute('''SELECT * FROM broadcasts WHERE (status = ? AND source = ?) ORDER BY tx_index ASC''', ('valid', feed_address))) initial_value = broadcasts[-1]['value'] # Record bet fulfillment. bindings = { 'id': util.make_id(tx0['tx_hash'], tx['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_hash'], 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'tx0_bet_type': tx0['bet_type'], 'tx1_bet_type': tx1['bet_type'], 'feed_address': tx1['feed_address'], 'initial_value': initial_value, 'deadline': tx1['deadline'], 'target_value': tx1['target_value'], 'leverage': tx1['leverage'], 'forward_quantity': forward_quantity, 'backward_quantity': backward_quantity, 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': max(tx0['block_index'], tx1['block_index']), 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': min(tx0['expire_index'], tx1['expire_index']), 'fee_fraction_int': tx1['fee_fraction_int'], 'status': 'pending', } sql='insert into bet_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :tx0_bet_type, :tx1_bet_type, :feed_address, :initial_value, :deadline, :target_value, :leverage, :forward_quantity, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_fraction_int, :status)' cursor.execute(sql, bindings) cursor.close() return