Esempio n. 1
0
    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
Esempio n. 2
0
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()
Esempio n. 3
0
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()
Esempio n. 4
0
    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
Esempio n. 5
0
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()
Esempio n. 6
0
 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)
Esempio n. 7
0
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()
Esempio n. 8
0
 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)
Esempio n. 9
0
 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)
Esempio n. 10
0
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()
Esempio n. 11
0
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()
Esempio n. 12
0
 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)
Esempio n. 13
0
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()
Esempio n. 14
0
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
Esempio n. 15
0
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()
Esempio n. 16
0
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
Esempio n. 17
0
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()
Esempio n. 18
0
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()
Esempio n. 19
0
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()
Esempio n. 20
0
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()
Esempio n. 21
0
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
Esempio n. 22
0
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()
Esempio n. 23
0
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()
Esempio n. 24
0
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