Example #1
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,
                    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()
Example #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,
                bet_match['tx0_address'],
                config.XCP,
                bet_match['forward_quantity'],
                action='recredit forward quantity',
                event=bet_match['id'])

    # Recredit tx1 address.
    util.credit(db,
                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()
Example #3
0
def dispense(db, tx):
    cursor = db.cursor()
    cursor.execute('SELECT * FROM dispensers WHERE source=:source AND status=:status', {
        'source': tx['destination'],
        'status': STATUS_OPEN
    })
    dispensers = cursor.fetchall()

    for dispenser in dispensers:
        must_give = int(floor(tx['btc_amount'] / dispenser['satoshirate']))
        remaining = int(floor(dispenser['give_remaining'] / dispenser['give_quantity']))
        actually_given = min(must_give, remaining) * dispenser['give_quantity']
        give_remaining = dispenser['give_remaining'] - actually_given

        assert give_remaining >= 0

        util.credit(db, tx['source'], dispenser['asset'], actually_given, action='dispense', event=tx['tx_hash'])
        #print(cursor.execute('select * from credits where address=? and calling_function=?', (tx['source'],'dispense')).fetchall())
        dispenser['give_remaining'] = give_remaining
        if give_remaining < dispenser['give_quantity']:
            # close the dispenser
            dispenser['give_remaining'] = 0
            if give_remaining > 0:
                # return the remaining to the owner
                util.credit(db, dispenser['source'], dispenser['asset'], give_remaining, action='dispenser close', event=tx['tx_hash'])
            dispenser['status'] = STATUS_CLOSED

        cursor.execute('UPDATE DISPENSERS SET give_remaining=:give_remaining, status=:status \
                WHERE source=:source AND asset=:asset AND satoshirate=:satoshirate AND give_quantity=:give_quantity', dispenser)

    cursor.close()
def cancel_order (db, order, status, block_index):
    cursor = db.cursor()

    # Update status of order.
    bindings = {
        'status': status,
        'tx_hash': order['tx_hash']
    }
    sql='update orders set status = :status where tx_hash = :tx_hash'
    cursor.execute(sql, bindings)
    log.message(db, block_index, 'update', 'orders', bindings)

    if order['give_asset'] != config.BTC:    # Can’t credit BTC.
        util.credit(db, 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()
Example #5
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['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,
    }
    if "integer overflow" not in status:
        sql = 'insert into btcpays values(:tx_index, :tx_hash, :block_index, :source, :destination, :btc_amount, :order_match_id, :status)'
        cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [btcpay] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))


    cursor.close()
Example #6
0
def parse (db, tx, message):
    dividend_parse_cursor = db.cursor()

    # Unpack message.
    try:
        if (tx['block_index'] > 288150 or config.TESTNET) and len(message) == LENGTH_2:
            quantity_per_unit, asset_id, dividend_asset_id = struct.unpack(FORMAT_2, message)
            asset = util.get_asset_name(db, asset_id, tx['block_index'])
            dividend_asset = util.get_asset_name(db, dividend_asset_id, tx['block_index'])
            status = 'valid'
        elif len(message) == LENGTH_1:
            quantity_per_unit, asset_id = struct.unpack(FORMAT_1, message)
            asset = util.get_asset_name(db, asset_id, tx['block_index'])
            dividend_asset = config.XCP
            status = 'valid'
        else:
            raise exceptions.UnpackError
    except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e:
        dividend_asset, quantity_per_unit, asset = None, None, None
        status = 'invalid: could not unpack'

    if dividend_asset == config.BTC:
        status = 'invalid: cannot pay {} dividends within protocol'.format(config.BTC)

    if status == 'valid':
        # For SQLite3
        quantity_per_unit = min(quantity_per_unit, config.MAX_INT)

        dividend_total, outputs, problems, fee = validate(db, tx['source'], quantity_per_unit, asset, dividend_asset, block_index=tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)

    if status == 'valid':
        # Debit.
        util.debit(db, tx['source'], dividend_asset, dividend_total, action='dividend', event=tx['tx_hash'])
        if tx['block_index'] >= 330000 or config.TESTNET: # Protocol change.
            util.debit(db, tx['source'], config.XCP, fee, action='dividend fee', event=tx['tx_hash'])

        # Credit.
        for output in outputs:
            util.credit(db, output['address'], dividend_asset, output['dividend_quantity'], action='dividend', event=tx['tx_hash'])

    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'source': tx['source'],
        'asset': asset,
        'dividend_asset': dividend_asset,
        'quantity_per_unit': quantity_per_unit,
        'fee_paid': fee,
        'status': status,
    }

    sql='insert into dividends values(:tx_index, :tx_hash, :block_index, :source, :asset, :dividend_asset, :quantity_per_unit, :fee_paid, :status)'
    dividend_parse_cursor.execute(sql, bindings)

    dividend_parse_cursor.close()
Example #7
0
def dispense(db, tx):
    cursor = db.cursor()
    cursor.execute('SELECT * FROM dispensers WHERE source=:source AND status=:status ORDER BY asset', {
        'source': tx['destination'],
        'status': STATUS_OPEN
    })
    dispensers = cursor.fetchall()

    dispense_index = 0
    for dispenser in dispensers:
        satoshirate = dispenser['satoshirate']
        give_quantity = dispenser['give_quantity']

        if satoshirate > 0 and give_quantity > 0:
            must_give = int(floor(tx['btc_amount'] / satoshirate))
            remaining = int(floor(dispenser['give_remaining'] / give_quantity))
            actually_given = min(must_give, remaining) * give_quantity
            give_remaining = dispenser['give_remaining'] - actually_given

            assert give_remaining >= 0

            # Skip dispense if quantity is 0
            if util.enabled('zero_quantity_value_adjustment_1') and actually_given==0:
                continue

            util.credit(db, tx['source'], dispenser['asset'], actually_given, action='dispense', event=tx['tx_hash'])

            dispenser['give_remaining'] = give_remaining
            if give_remaining < dispenser['give_quantity']:
                # close the dispenser
                dispenser['give_remaining'] = 0
                if give_remaining > 0:
                    # return the remaining to the owner
                    util.credit(db, dispenser['source'], dispenser['asset'], give_remaining, action='dispenser close', event=tx['tx_hash'])
                dispenser['status'] = STATUS_CLOSED

            dispenser['block_index'] = tx['block_index']
            dispenser['prev_status'] = STATUS_OPEN
            cursor.execute('UPDATE DISPENSERS SET give_remaining=:give_remaining, status=:status \
                    WHERE source=:source AND asset=:asset AND satoshirate=:satoshirate AND give_quantity=:give_quantity AND status=:prev_status', dispenser)

            bindings = {
                'tx_index': tx['tx_index'],
                'tx_hash': tx['tx_hash'],
                'dispense_index': dispense_index,
                'block_index': tx['block_index'],
                'source': tx['destination'],
                'destination': tx['source'],
                'asset': dispenser['asset'],
                'dispense_quantity': actually_given,
                'dispenser_tx_hash': dispenser['tx_hash']
            }
            sql = 'INSERT INTO dispenses(tx_index, dispense_index, tx_hash, block_index, source, destination, asset, dispense_quantity, dispenser_tx_hash) \
                    VALUES(:tx_index, :dispense_index, :tx_hash, :block_index, :source, :destination, :asset, :dispense_quantity, :dispenser_tx_hash);'
            cursor.execute(sql, bindings)
            dispense_index += 1

    cursor.close()
        def create_contract(self, code, endowment=0, sender=''):
            if not sender:
                sender = '82a978b3f5962a5b0957d9ee9eef472ee55b42f1' # PyEthereum uses ECDSA to derive this string from `sender = 0`.

            util.credit(db, sender, config.XCP, max(endowment*2, 100000000), action='unit test', event='facefeed')

            success, data = tester.state.do_send(self, sender, '', endowment, data=code)
            contract_id = data
            return contract_id
Example #9
0
def dispense(db, tx):
    cursor = db.cursor()
    cursor.execute('SELECT * FROM dispensers WHERE source=:source AND status=:status', {
        'source': tx['destination'],
        'status': STATUS_OPEN
    })
    dispensers = cursor.fetchall()

    dispense_logs = []

    for dispenser in dispensers:

        # They should be rejected in `validate()`.
        assert dispenser['satoshirate'] > 0
        assert dispenser['give_quantity'] > 0

        must_give = int(floor(tx['btc_amount'] / dispenser['satoshirate']))
        remaining = int(floor(dispenser['give_remaining'] / dispenser['give_quantity']))
        actually_given = min(must_give, remaining) * dispenser['give_quantity']
        give_remaining = dispenser['give_remaining'] - actually_given

        assert give_remaining >= 0

        # Skip dispense if quantity is 0
        if util.enabled('zero_quantity_value_adjustment_1') and actually_given==0:
            continue

        util.credit(db, tx['source'], dispenser['asset'], actually_given, action='dispense', event=tx['tx_hash'])
        dispense_logs.append({
            "tx_index": tx['tx_index'],
            "tx_hash": tx['tx_hash'],
            "block_index": tx['block_index'],
            "source": tx['source'],
            "destination": tx['destination'],
            "asset": dispenser['asset'],
            "must_give": must_give,
            "remaining": remaining,
            "actually_given": actually_given,
            "satoshirate": dispenser['satoshirate'],
            "dispenser_tx_hash": dispenser['tx_hash']
        })
        dispenser['give_remaining'] = give_remaining
        if give_remaining < dispenser['give_quantity']:
            # close the dispenser
            dispenser['give_remaining'] = 0
            if give_remaining > 0:
                # return the remaining to the owner
                util.credit(db, dispenser['source'], dispenser['asset'], give_remaining, action='dispenser close', event=tx['tx_hash'])
            dispenser['status'] = STATUS_CLOSED

        dispenser['block_index'] = tx['block_index']
        dispenser['prev_status'] = STATUS_OPEN
        cursor.execute('UPDATE DISPENSERS SET give_remaining=:give_remaining, status=:status \
                WHERE source=:source AND asset=:asset AND satoshirate=:satoshirate AND give_quantity=:give_quantity AND status=:prev_status', dispenser)

    for log in dispense_logs:
        cursor.execute('INSERT INTO DISPENSES VALUES (:tx_index, :tx_hash, :block_index, :source, :destination,  :asset, :must_give, :remaining, :actually_given, :satoshirate, :dispenser_tx_hash)', log)
        def send (self, sender, to, value, data=[]):
            # print('tuple', sender, to, value, data)

            # Encode data.
            data = serpent.encode_datalist(data)
            data = binascii.unhexlify(data)

            # Execute contract.
            # print('qux', data, type(data))
            util.credit(db, sender, config.XCP, value + 100000000, action='unit test', event='facefeed')
            success, output = tester.state.do_send(self, sender, to, value, data=data)
            if output:
                return rlp.decode_datalist(bytes(output))
            else:
                return []
Example #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, rps['source'], 'XCP', rps['wager'], action='recredit wager', event=rps['tx_hash'])

    cursor.close()
Example #12
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, bet['source'], config.XCP, bet['wager_remaining'], action='recredit wager remaining', event=bet['tx_hash'])

    cursor = db.cursor()
Example #13
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, rps['source'], 'XCP', rps['wager'], action='recredit wager', event=rps['tx_hash'])

    cursor.close()
Example #14
0
        def create_contract(self, code, endowment=0, sender=''):
            if not sender:
                sender = '82a978b3f5962a5b0957d9ee9eef472ee55b42f1'  # PyEthereum uses ECDSA to derive this string from `sender = 0`.

            util.credit(db,
                        sender,
                        config.XCP,
                        max(endowment * 2, 100000000),
                        action='unit test',
                        event='facefeed')

            success, data = tester.state.do_send(self,
                                                 sender,
                                                 '',
                                                 endowment,
                                                 data=code)
            contract_id = data
            return contract_id
 def transfer_value(self,
                    tx,
                    source,
                    destination,
                    quantity,
                    asset=config.XCP):
     if source:
         util.debit(self.db,
                    source,
                    asset,
                    quantity,
                    action='transfer value',
                    event=tx.tx_hash)
     if destination:
         util.credit(self.db,
                     destination,
                     asset,
                     quantity,
                     action='transfer value',
                     event=tx.tx_hash)
     return True
Example #16
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, rps_match['tx0_address'], 'XCP',
                    rps_match['wager'], action='recredit wager', event=rps_match['id'])
        # Recredit tx1 address.
        util.credit(db, 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, 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()
Example #17
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, rps_match['tx0_address'], 'XCP',
                    rps_match['wager'], action='recredit wager', event=rps_match['id'])
        # Recredit tx1 address.
        util.credit(db, 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, 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()
Example #18
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, bet_match['tx0_address'], config.XCP,
                bet_match['forward_quantity'], action='recredit forward quantity', event=bet_match['id'])

    # Recredit tx1 address.
    util.credit(db, 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()
Example #19
0
        def send(self, sender, to, value, data=[]):
            # print('tuple', sender, to, value, data)

            # Encode data.
            data = serpent.encode_datalist(data)
            data = binascii.unhexlify(data)

            # Execute contract.
            # print('qux', data, type(data))
            util.credit(db,
                        sender,
                        config.XCP,
                        value + 100000000,
                        action='unit test',
                        event='facefeed')
            success, output = tester.state.do_send(self,
                                                   sender,
                                                   to,
                                                   value,
                                                   data=data)
            if output:
                return rlp.decode_datalist(bytes(output))
            else:
                return []
Example #20
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 or config.REGTEST:  # 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']
                                     ))  # 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 or config.REGTEST:  # 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 or config.REGTEST:  # 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'])
        tx1_price = util.price(tx1['get_quantity'], tx1['give_quantity'])
        tx1_inverse_price = util.price(tx1['give_quantity'],
                                       tx1['get_quantity'])

        # Protocol change.
        if tx['block_index'] < 286000:
            tx1_inverse_price = util.price(1, tx1_price)

        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))))
            forward_quantity = int(
                min(tx0_give_remaining,
                    int(util.price(tx1_give_remaining, tx0_price))))
            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 or config.REGTEST:  # 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 or config.REGTEST:  # 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 or config.REGTEST:  # 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 or config.REGTEST:  # Protocol change.
                        fee = int(tx1['fee_required'] * util.price(
                            backward_quantity, tx1['give_quantity']))
                    else:
                        fee = int(
                            tx1['fee_required_remaining'] *
                            util.price(forward_quantity, tx1_get_remaining))

                    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 or config.REGTEST:  # Protocol change.
                            tx1_fee_required_remaining -= fee

                elif tx1['give_asset'] == config.BTC:

                    if block_index >= 310500 or config.TESTNET or config.REGTEST:  # Protocol change.
                        fee = int(tx0['fee_required'] * util.price(
                            backward_quantity, tx0['give_quantity']))
                    else:
                        fee = int(
                            tx0['fee_required_remaining'] *
                            util.price(backward_quantity, tx0_get_remaining))

                    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 or config.REGTEST:  # 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,
                            tx1['source'],
                            tx1['get_asset'],
                            forward_quantity,
                            action='order match',
                            event=order_match_id)
                util.credit(db,
                            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
                 or config.REGTEST)):  # 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,
                                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
                 or config.REGTEST)):  # 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,
                                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 or config.REGTEST:  # Protocol change.
                match_expire_index = block_index + 20
            elif block_index >= 286500 or config.TESTNET or config.REGTEST:  # 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
Example #21
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
            or config.REGTEST):  # 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,
                        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
                or config.REGTEST):  # 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,
                        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
                or config.REGTEST):  # 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 or config.REGTEST:  # 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 or config.REGTEST:  # Protocol change.
        if not (block_index >= 315000 or config.TESTNET
                or config.REGTEST):  # 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()
Example #22
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']))   # 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'])
        tx1_price = util.price(tx1['get_quantity'], tx1['give_quantity'])
        tx1_inverse_price = util.price(tx1['give_quantity'], tx1['get_quantity'])

        # Protocol change.
        if tx['block_index'] < 286000: tx1_inverse_price = util.price(1, tx1_price)

        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))))
            forward_quantity = int(min(tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price))))
            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']))
                    else:
                        fee = int(tx1['fee_required_remaining'] * util.price(forward_quantity, tx1_get_remaining))

                    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']))
                    else:
                        fee = int(tx0['fee_required_remaining'] * util.price(backward_quantity, tx0_get_remaining))

                    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, tx1['source'], tx1['get_asset'],
                                    forward_quantity, action='order match', event=order_match_id)
                util.credit(db, 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, 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, tx1['source'], tx1['give_asset'], tx1_give_remaining, event=tx0['tx_hash'], action='filled')
            bindings = {
                'give_remaining': tx1_give_remaining,
                'get_remaining': tx1_get_remaining,
                'fee_required_remaining': tx1_fee_required_remaining,
                'fee_provided_remaining': tx1_fee_provided_remaining,
                'status': tx1_status,
                'tx_hash': tx1['tx_hash']
            }
            sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash'
            cursor.execute(sql, bindings)
            log.message(db, block_index, 'update', 'orders', bindings)

            # Calculate when the match will expire.
            if block_index >= 308000 or config.TESTNET:      # Protocol change.
                match_expire_index = block_index + 20
            elif block_index >= 286500 or config.TESTNET:    # Protocol change.
                match_expire_index = block_index + 10
            else:
                match_expire_index = min(tx0['expire_index'], tx1['expire_index'])

            # Record order match.
            bindings = {
                'id': util.make_id(tx0['tx_hash'], tx['tx_hash']),
                'tx0_index': tx0['tx_index'],
                'tx0_hash': tx0['tx_hash'],
                'tx0_address': tx0['source'],
                'tx1_index': tx1['tx_index'],
                'tx1_hash': tx1['tx_hash'],
                'tx1_address': tx1['source'],
                'forward_asset': forward_asset,
                'forward_quantity': forward_quantity,
                'backward_asset': backward_asset,
                'backward_quantity': backward_quantity,
                'tx0_block_index': tx0['block_index'],
                'tx1_block_index': tx1['block_index'],
                'block_index': block_index,
                'tx0_expiration': tx0['expiration'],
                'tx1_expiration': tx1['expiration'],
                'match_expire_index': match_expire_index,
                'fee_paid': fee,
                'status': status,
            }
            sql='insert into order_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :forward_asset, :forward_quantity, :backward_asset, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_paid, :status)'
            cursor.execute(sql, bindings)

            if tx1_status == 'filled':
                break

    cursor.close()
    return
 def transfer_value(self, tx, source, destination, quantity, asset=config.XCP):
     if source:
         util.debit(self.db, source, asset, quantity, action='transfer value', event=tx.tx_hash)
     if destination:
         util.credit(self.db, destination, asset, quantity, action='transfer value', event=tx.tx_hash)
     return True
Example #24
0
def parse (db, tx, message):
    issuance_parse_cursor = db.cursor()

    # Unpack message.
    try:
        if (tx['block_index'] > 283271 or config.TESTNET) and len(message) >= LENGTH_2: # Protocol change.
            if len(message) - LENGTH_2 <= 42:
                curr_format = FORMAT_2 + '{}p'.format(len(message) - LENGTH_2)
            else:
                curr_format = FORMAT_2 + '{}s'.format(len(message) - LENGTH_2)
            asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(curr_format, message)

            call_price = round(call_price, 6) # TODO: arbitrary
            try:
                description = description.decode('utf-8')
            except UnicodeDecodeError:
                description = ''
        else:
            if len(message) != LENGTH_1:
                raise exceptions.UnpackError
            asset_id, quantity, divisible = struct.unpack(FORMAT_1, message)
            callable_, call_date, call_price, description = False, 0, 0.0, ''
        try:
            asset = util.generate_asset_name(asset_id, tx['block_index'])
        except exceptions.AssetNameError:
            asset = None
            status = 'invalid: bad asset name'
        status = 'valid'
    except exceptions.UnpackError as e:
        asset, quantity, divisible, callable_, call_date, call_price, description = None, None, None, None, None, None, None
        status = 'invalid: could not unpack'

    fee = 0
    if status == 'valid':
        call_date, call_price, problems, fee, description, divisible, reissuance = validate(db, tx['source'], tx['destination'], asset, quantity, divisible, callable_, call_date, call_price, description, block_index=tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)
        if not util.enabled('integer_overflow_fix', block_index=tx['block_index']) and 'total quantity overflow' in problems:
            quantity = 0

    if tx['destination']:
        issuer = tx['destination']
        transfer = True
        quantity = 0
    else:
        issuer = tx['source']
        transfer = False

    # Debit fee.
    if status == 'valid':
        util.debit(db, tx['source'], config.XCP, fee, action="issuance fee", event=tx['tx_hash'])

    # Lock?
    lock = False
    if status == 'valid':
        if description and description.lower() == 'lock':
            lock = True
            cursor = db.cursor()
            issuances = list(cursor.execute('''SELECT * FROM issuances \
                                               WHERE (status = ? AND asset = ?)
                                               ORDER BY tx_index ASC''', ('valid', asset)))
            cursor.close()
            description = issuances[-1]['description']  # Use last description. (Assume previous issuance exists because tx is valid.)
            timestamp, value_int, fee_fraction_int = None, None, None

        if not reissuance:
            # Add to table of assets.
            bindings= {
                'asset_id': str(asset_id),
                'asset_name': str(asset),
                'block_index': tx['block_index'],
            }
            sql='insert into assets values(:asset_id, :asset_name, :block_index)'
            issuance_parse_cursor.execute(sql, bindings)

    # Add parsed transaction to message-type–specific table.
    bindings= {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'asset': asset,
        'quantity': quantity,
        'divisible': divisible,
        'source': tx['source'],
        'issuer': issuer,
        'transfer': transfer,
        'callable': callable_,
        'call_date': call_date,
        'call_price': call_price,
        'description': description,
        'fee_paid': fee,
        'locked': lock,
        'status': status,
    }
    if "integer overflow" not in status:
        sql='insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status)'
        issuance_parse_cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [issuance] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    # Credit.
    if status == 'valid' and quantity:
        util.credit(db, tx['source'], asset, quantity, action="issuance", event=tx['tx_hash'])

    issuance_parse_cursor.close()
Example #25
0
def parse(db, tx, message):
    issuance_parse_cursor = db.cursor()

    # Unpack message.
    try:
        if (tx['block_index'] > 283271 or config.TESTNET
            ) and len(message) >= LENGTH_2:  # Protocol change.
            if len(message) - LENGTH_2 <= 42:
                curr_format = FORMAT_2 + '{}p'.format(len(message) - LENGTH_2)
            else:
                curr_format = FORMAT_2 + '{}s'.format(len(message) - LENGTH_2)
            asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(
                curr_format, message)

            call_price = round(call_price, 6)  # TODO: arbitrary
            try:
                description = description.decode('utf-8')
            except UnicodeDecodeError:
                description = ''
        else:
            if len(message) != LENGTH_1:
                raise exceptions.UnpackError
            asset_id, quantity, divisible = struct.unpack(FORMAT_1, message)
            callable_, call_date, call_price, description = False, 0, 0.0, ''
        try:
            asset = util.generate_asset_name(asset_id, tx['block_index'])
        except exceptions.AssetNameError:
            asset = None
            status = 'invalid: bad asset name'
        status = 'valid'
    except exceptions.UnpackError as e:
        asset, quantity, divisible, callable_, call_date, call_price, description = None, None, None, None, None, None, None
        status = 'invalid: could not unpack'

    fee = 0
    if status == 'valid':
        call_date, call_price, problems, fee, description, divisible, reissuance = validate(
            db,
            tx['source'],
            tx['destination'],
            asset,
            quantity,
            divisible,
            callable_,
            call_date,
            call_price,
            description,
            block_index=tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)
        if 'total quantity overflow' in problems:
            quantity = 0

    if tx['destination']:
        issuer = tx['destination']
        transfer = True
        quantity = 0
    else:
        issuer = tx['source']
        transfer = False

    # Debit fee.
    if status == 'valid':
        util.debit(db,
                   tx['source'],
                   config.XCP,
                   fee,
                   action="issuance fee",
                   event=tx['tx_hash'])

    # Lock?
    lock = False
    if status == 'valid':
        if description and description.lower() == 'lock':
            lock = True
            cursor = db.cursor()
            issuances = list(
                cursor.execute(
                    '''SELECT * FROM issuances \
                                               WHERE (status = ? AND asset = ?)
                                               ORDER BY tx_index ASC''',
                    ('valid', asset)))
            cursor.close()
            description = issuances[-1][
                'description']  # Use last description. (Assume previous issuance exists because tx is valid.)
            timestamp, value_int, fee_fraction_int = None, None, None

        if not reissuance:
            # Add to table of assets.
            bindings = {
                'asset_id': str(asset_id),
                'asset_name': str(asset),
                'block_index': tx['block_index'],
            }
            sql = 'insert into assets values(:asset_id, :asset_name, :block_index)'
            issuance_parse_cursor.execute(sql, bindings)

    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'asset': asset,
        'quantity': quantity,
        'divisible': divisible,
        'source': tx['source'],
        'issuer': issuer,
        'transfer': transfer,
        'callable': callable_,
        'call_date': call_date,
        'call_price': call_price,
        'description': description,
        'fee_paid': fee,
        'locked': lock,
        'status': status,
    }
    sql = 'insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status)'
    issuance_parse_cursor.execute(sql, bindings)

    # Credit.
    if status == 'valid' and quantity:
        util.credit(db,
                    tx['source'],
                    asset,
                    quantity,
                    action="issuance",
                    event=tx['tx_hash'])

    issuance_parse_cursor.close()
Example #26
0
def parse(db, tx, message):
    try:
        unpacked = unpack(db, message, tx['block_index'])
        status = 'valid'
    except (struct.error) as e:
        status = 'invalid: truncated message'
    except (exceptions.AssetNameError, exceptions.AssetIDError) as e:
        status = 'invalid: invalid asset name/id'
    except (Exception) as e:
        status = 'invalid: couldn\'t unpack; %s' % e

    cursor = db.cursor()

    plain_sends = []
    all_debits = []
    all_credits = []
    if status == 'valid':
        for asset_id in unpacked:
            try:
                asset = util.get_asset_name(db, asset_id, tx['block_index'])
            except (exceptions.AssetNameError) as e:
                status = 'invalid: asset %s invalid at block index %i' % (
                    asset_id, tx['block_index'])
                break

            cursor.execute(
                '''SELECT * FROM balances \
                              WHERE (address = ? AND asset = ?)''',
                (tx['source'], asset_id))

            balances = cursor.fetchall()
            if not balances:
                status = 'invalid: insufficient funds for asset %s, address %s has no balance' % (
                    asset_id, tx['source'])
                break

            credits = unpacked[asset_id]

            total_sent = reduce(lambda p, t: p + t[1], credits, 0)

            if balances[0]['quantity'] < total_sent:
                status = 'invalid: insufficient funds for asset %s, needs %i' % (
                    asset_id, total_sent)
                break

            if status == 'valid':
                plain_sends += map(lambda t: util.py34TupleAppend(asset_id, t),
                                   credits)
                all_credits += map(
                    lambda t: {
                        "asset": asset_id,
                        "destination": t[0],
                        "quantity": t[1]
                    }, credits)
                all_debits.append({"asset": asset_id, "quantity": total_sent})

    if status == 'valid':
        problems = validate(db, tx['source'], plain_sends, tx['block_index'])

        if problems: status = 'invalid:' + '; '.join(problems)

    if status == 'valid':
        for op in all_credits:
            util.credit(db,
                        op['destination'],
                        op['asset'],
                        op['quantity'],
                        action='mpma send',
                        event=tx['tx_hash'])

        for op in all_debits:
            util.debit(db,
                       tx['source'],
                       op['asset'],
                       op['quantity'],
                       action='mpma send',
                       event=tx['tx_hash'])

        # Enumeration of the plain sends needs to be deterministic, so we sort them by asset and then by address
        plain_sends = sorted(plain_sends, key=lambda x: ''.join([x[0], x[1]]))
        for i, op in enumerate(plain_sends):
            if len(op) > 3:
                memo_bytes = op[3]
            else:
                memo_bytes = None

            bindings = {
                'tx_index': tx['tx_index'],
                'tx_hash': tx['tx_hash'],
                'block_index': tx['block_index'],
                'source': tx['source'],
                'asset': op[0],
                'destination': op[1],
                'quantity': op[2],
                'status': status,
                'memo': memo_bytes,
                'msg_index': i
            }

            sql = 'insert into sends (tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo, msg_index) values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo, :msg_index)'
            cursor.execute(sql, bindings)

    if status != 'valid':
        logger.warn("Not storing [mpma] tx [%s]: %s" % (tx['tx_hash'], status))

    cursor.close()
def parse (db, tx, message):
    cursor = db.cursor()

    # Unpack message.
    try:
        unpacked = unpack(db, message, tx['block_index'])
        asset, quantity, destination, memo_bytes = unpacked['asset'], unpacked['quantity'], unpacked['address'], unpacked['memo']

        status = 'valid'

    except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e:
        asset, quantity, destination, memo_bytes = None, None, None, None
        status = 'invalid: could not unpack ({})'.format(e)
    except:
        asset, quantity, destination, memo_bytes = None, None, None, None
        status = 'invalid: could not unpack'

    if status == 'valid':
        # don't allow sends over MAX_INT at all
        if quantity and quantity > config.MAX_INT:
            status = 'invalid: quantity is too large'
            quantity = None

    if status == 'valid':
        problems = validate(db, tx['source'], destination, asset, quantity, memo_bytes, tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)

    if status == 'valid':
        # verify balance is present
        cursor.execute('''SELECT * FROM balances \
                                     WHERE (address = ? AND asset = ?)''', (tx['source'], asset))
        balances = cursor.fetchall()
        if not balances or balances[0]['quantity'] < quantity:
            status = 'invalid: insufficient funds'

    if status == 'valid':
        util.debit(db, tx['source'], asset, quantity, action='send', event=tx['tx_hash'])
        util.credit(db, destination, asset, quantity, action='send', event=tx['tx_hash'])

    # log invalid transactions
    if status != 'valid':
        if quantity is None:
            logger.warn("Invalid send from %s with status %s. (%s)" % (tx['source'], status, tx['tx_hash']))
        else:
            logger.warn("Invalid send of %s %s from %s to %s. status is %s. (%s)" % (quantity, asset, tx['source'], destination, status, tx['tx_hash']))


    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'source': tx['source'],
        'destination': destination,
        'asset': asset,
        'quantity': quantity,
        'status': status,
        'memo': memo_bytes,
    }
    if "integer overflow" not in status and "quantity must be in satoshis" not in status:
        sql = 'insert into sends values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo)'
        cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [send] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))


    cursor.close()
Example #28
0
def parse (db, tx, MAINNET_BURNS, message=None):
    burn_parse_cursor = db.cursor()

    if config.TESTNET or config.REGTEST:
        problems = []
        status = 'valid'

        if status == 'valid':
            problems = validate(db, tx['source'], tx['destination'], tx['btc_amount'], tx['block_index'], overburn=False)
            if problems: status = 'invalid: ' + '; '.join(problems)

            if tx['btc_amount'] != None:
                sent = tx['btc_amount']
            else:
                sent = 0

        if status == 'valid':
            # Calculate quantity of XCP earned. (Maximum 1 BTC in total, ever.)
            cursor = db.cursor()
            cursor.execute('''SELECT * FROM burns WHERE (status = ? AND source = ?)''', ('valid', tx['source']))
            burns = cursor.fetchall()
            already_burned = sum([burn['burned'] for burn in burns])
            ONE = 1 * config.UNIT
            max_burn = ONE - already_burned
            if sent > max_burn: burned = max_burn   # Exceeded maximum burn; earn what you can.
            else: burned = sent

            total_time = config.BURN_END - config.BURN_START
            partial_time = config.BURN_END - tx['block_index']
            multiplier = (1000 + (500 * Fraction(partial_time, total_time)))
            earned = round(burned * multiplier)

            # Credit source address with earned XCP.
            util.credit(db, tx['source'], config.XCP, earned, action='burn', event=tx['tx_hash'])
        else:
            burned = 0
            earned = 0

        tx_index = tx['tx_index']
        tx_hash = tx['tx_hash']
        block_index = tx['block_index']
        source = tx['source']

    else:
        # Mainnet burns are hard‐coded.

        try:
            line = MAINNET_BURNS[tx['tx_hash']]
        except KeyError:
            return

        util.credit(db, line['source'], config.XCP, int(line['earned']), action='burn', event=line['tx_hash'])

        tx_index = tx['tx_index']
        tx_hash = line['tx_hash']
        block_index = line['block_index']
        source = line['source']
        burned = line['burned']
        earned = line['earned']
        status = 'valid'

    # Add parsed transaction to message-type–specific table.
    # TODO: store sent in table
    bindings = {
        'tx_index': tx_index,
        'tx_hash': tx_hash,
        'block_index': block_index,
        'source': source,
        'burned': burned,
        'earned': earned,
        'status': status,
    }
    if "integer overflow" not in status:
        sql = 'insert into burns values(:tx_index, :tx_hash, :block_index, :source, :burned, :earned, :status)'
        burn_parse_cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [burn] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    burn_parse_cursor.close()
Example #29
0
def parse (db, tx, message):
    cursor = db.cursor()

    fee_paid = round(config.UNIT/2)

    # Unpack message.
    try:
        unpacked = unpack(db, message, tx['block_index'])
        destination, flags, memo_bytes = unpacked['destination'], unpacked['flags'], unpacked['memo']

        status = 'valid'
    except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e:
        destination, flags, memo_bytes = None, None, None
        status = 'invalid: could not unpack ({})'.format(e)
    except BalanceError:
        destination, flags, memo_bytes = None, None, None
        status = 'invalid: insufficient balance for antispam fee for sweep'
    except Exception as err:
        destination, flags, memo_bytes = None, None, None
        status = 'invalid: could not unpack, ' + str(err)

    if status == 'valid':
        problems = validate(db, tx['source'], destination, flags, memo_bytes, tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)

        try:
            util.debit(db, tx['source'], config.XCP, fee_paid, action='sweep fee', event=tx['tx_hash'])
        except BalanceError:
            destination, flags, memo_bytes = None, None, None
            status = 'invalid: insufficient balance for antispam fee for sweep'

    if status == 'valid':
        cursor.execute('''SELECT * FROM balances WHERE address = ?''', (tx['source'],))
        balances = cursor.fetchall()
        if flags & FLAG_BALANCES:
            for balance in balances:
                util.debit(db, tx['source'], balance['asset'], balance['quantity'], action='sweep', event=tx['tx_hash'])
                util.credit(db, destination, balance['asset'], balance['quantity'], action='sweep', event=tx['tx_hash'])

        if flags & FLAG_OWNERSHIP:
            for balance in balances:
                cursor.execute('''SELECT * FROM issuances \
                                  WHERE (status = ? AND asset = ?)
                                  ORDER BY tx_index ASC''', ('valid', balance['asset']))
                issuances = cursor.fetchall()
                if len(issuances) > 0:
                    last_issuance = issuances[-1]
                    if last_issuance['issuer'] == tx['source']:
                        bindings= {
                            'tx_index': tx['tx_index'],
                            'tx_hash': tx['tx_hash'],
                            'block_index': tx['block_index'],
                            'asset': balance['asset'],
                            'quantity': 0,
                            'divisible': last_issuance['divisible'],
                            'source': last_issuance['source'],
                            'issuer': destination,
                            'transfer': True,
                            'callable': last_issuance['callable'],
                            'call_date': last_issuance['call_date'],
                            'call_price': last_issuance['call_price'],
                            'description': last_issuance['description'],
                            'fee_paid': 0,
                            'locked': last_issuance['locked'],
                            'status': status,
                            'asset_longname': last_issuance['asset_longname'],
                        }
                        sql='insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname)'
                        cursor.execute(sql, bindings)

        bindings = {
            'tx_index': tx['tx_index'],
            'tx_hash': tx['tx_hash'],
            'block_index': tx['block_index'],
            'source': tx['source'],
            'destination': destination,
            'flags': flags,
            'status': status,
            'memo': memo_bytes,
            'fee_paid': fee_paid
        }
        sql = 'insert into sweeps values(:tx_index, :tx_hash, :block_index, :source, :destination, :flags, :status, :memo, :fee_paid)'
        cursor.execute(sql, bindings)

    cursor.close()
Example #30
0
def parse(db, tx, message):
    cursor = db.cursor()

    # Unpack message.
    status = 'valid'  # might be invalidate later.
    try:
        if util.enabled('broadcast_pack_text', tx['block_index']):
            timestamp, value, fee_fraction_int, rawtext = struct.unpack(
                FORMAT + '{}s'.format(len(message) - LENGTH), message)
            textlen = VarIntSerializer.deserialize(rawtext)
            if textlen == 0:
                text = b''
            else:
                text = rawtext[-textlen:]
                if len(text) != textlen:
                    status = 'invalid: text length mismatch'
        else:
            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 = ''
    except (struct.error):
        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,
    }
    if "integer overflow" not in 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)
    else:
        logger.warn("Not storing [broadcast] tx [%s]: %s" %
                    (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    # stop processing if broadcast is invalid for any reason
    if util.enabled('broadcast_invalid_check') and status != 'valid':
        return

    # Options? Should not fail to parse due to above checks.
    if util.enabled('options_require_memo') and text and text.lower(
    ).startswith('options'):
        options = util.parse_options_from_string(text)
        if options is not False:
            op_bindings = {
                'block_index': tx['block_index'],
                'address': tx['source'],
                'options': options
            }
            sql = 'insert or replace into addresses(address, options, block_index) values(:address, :options, :block_index)'
            cursor = db.cursor()
            cursor.execute(sql, op_bindings)

    # Negative values (default to ignore).
    if value is 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'])
        return

    # stop processing if broadcast is invalid for any reason
    # @TODO: remove this check once broadcast_invalid_check has been activated
    if util.enabled('max_fee_fraction') and status != 'valid':
        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']

        if util.enabled('inmutable_fee_fraction'):
            fee_fraction = bet_match['fee_fraction_int']
        else:
            fee_fraction = fee_fraction_int

        fee = int(fee_fraction * total_escrow / config.UNIT)  # 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']
                bear_escrow = bet_match['backward_quantity']
            else:
                bull_address = bet_match['tx1_address']
                bear_address = bet_match['tx0_address']
                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,
                                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,
                                bear_address,
                                config.XCP,
                                bear_credit,
                                action='bet {}'.format(bet_match_status),
                                event=tx['tx_hash'])

                # Pay fee to feed.
                util.credit(db,
                            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,
                            bull_address,
                            config.XCP,
                            bull_credit,
                            action='bet {}'.format(bet_match_status),
                            event=tx['tx_hash'])
                util.credit(db,
                            bear_address,
                            config.XCP,
                            bear_credit,
                            action='bet {}'.format(bet_match_status),
                            event=tx['tx_hash'])

                # Pay fee to feed.
                util.credit(db,
                            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,
                            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,
                            notequal_address,
                            config.XCP,
                            escrow_less_fee,
                            action='bet {}'.format(bet_match_status),
                            event=tx['tx_hash'])

            # Pay fee to feed.
            util.credit(db,
                        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)
Example #31
0
def parse(db, tx, message):
    cursor = db.cursor()

    # Unpack message.
    try:
        action_address = tx['source']
        assetid, give_quantity, escrow_quantity, mainchainrate, dispenser_status = struct.unpack(
            FORMAT, message[0:LENGTH])
        if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS:
            action_address = address.unpack(message[LENGTH:LENGTH + 21])
        asset = util.generate_asset_name(assetid, util.CURRENT_BLOCK_INDEX)
        status = 'valid'
    except (exceptions.UnpackError, struct.error) as e:
        assetid, give_quantity, mainchainrate, asset = None, None, None, None
        status = 'invalid: could not unpack'

    if status == 'valid':
        if dispenser_status == STATUS_OPEN or dispenser_status == STATUS_OPEN_EMPTY_ADDRESS:
            cursor.execute(
                'SELECT * FROM dispensers WHERE source=:source AND asset=:asset AND status=:status',
                {
                    'source': action_address,
                    'asset': asset,
                    'status': STATUS_OPEN
                })
            existing = cursor.fetchall()

            if len(existing) == 0:
                # Create the new dispenser
                try:
                    if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS:
                        cursor.execute(
                            'SELECT count(*) cnt FROM balances WHERE address=:address AND quantity > 0',
                            {'address': action_address})
                        counts = cursor.fetchall()[0]

                        if counts['cnt'] == 0:
                            util.debit(db,
                                       tx['source'],
                                       asset,
                                       escrow_quantity,
                                       action='open dispenser empty addr',
                                       event=tx['tx_hash'])
                            util.credit(db,
                                        action_address,
                                        asset,
                                        escrow_quantity,
                                        action='open dispenser empty addr',
                                        event=tx['tx_hash'])
                            util.debit(db,
                                       action_address,
                                       asset,
                                       escrow_quantity,
                                       action='open dispenser empty addr',
                                       event=tx['tx_hash'])
                        else:
                            status = 'invalid: address not empty'
                    else:
                        util.debit(db,
                                   tx['source'],
                                   asset,
                                   escrow_quantity,
                                   action='open dispenser',
                                   event=tx['tx_hash'])
                except util.DebitError as e:
                    status = 'invalid: insufficient funds'

                if status == 'valid':
                    bindings = {
                        'tx_index': tx['tx_index'],
                        'tx_hash': tx['tx_hash'],
                        'block_index': tx['block_index'],
                        'source': action_address,
                        'asset': asset,
                        'give_quantity': give_quantity,
                        'escrow_quantity': escrow_quantity,
                        'satoshirate': mainchainrate,
                        'status': STATUS_OPEN,
                        'give_remaining': escrow_quantity
                    }
                    sql = 'insert into dispensers values(:tx_index, :tx_hash, :block_index, :source, :asset, :give_quantity, :escrow_quantity, :satoshirate, :status, :give_remaining)'
                    cursor.execute(sql, bindings)
            elif len(existing) == 1 and existing[0][
                    'satoshirate'] == mainchainrate and existing[0][
                        'give_quantity'] == give_quantity:
                if tx["source"] == action_address:
                    # Refill the dispenser by the given amount
                    bindings = {
                        'source': tx['source'],
                        'asset': asset,
                        'status': dispenser_status,
                        'give_remaining':
                        existing[0]['give_remaining'] + escrow_quantity,
                        'status': STATUS_OPEN,
                        'block_index': tx['block_index']
                    }
                    try:
                        util.debit(db,
                                   tx['source'],
                                   asset,
                                   escrow_quantity,
                                   action='refill dispenser',
                                   event=tx['tx_hash'])
                        sql = 'UPDATE dispensers SET give_remaining=:give_remaining \
                            WHERE source=:source AND asset=:asset AND status=:status'

                        cursor.execute(sql, bindings)
                    except (util.DebitError):
                        status = 'insufficient funds'
                else:
                    status = 'invalid: can only refill dispenser from source'
            else:
                status = 'can only have one open dispenser per asset per address'
        elif dispenser_status == STATUS_CLOSED:
            cursor.execute(
                'SELECT give_remaining FROM dispensers WHERE source=:source AND asset=:asset AND status=:status',
                {
                    'source': tx['source'],
                    'asset': asset,
                    'status': STATUS_OPEN
                })
            existing = cursor.fetchall()

            if len(existing) == 1:
                util.credit(db,
                            tx['source'],
                            asset,
                            existing[0]['give_remaining'],
                            action='close dispenser',
                            event=tx['tx_hash'])
                bindings = {
                    'source': tx['source'],
                    'asset': asset,
                    'status': STATUS_CLOSED,
                    'block_index': tx['block_index']
                }
                sql = 'UPDATE dispensers SET give_remaining=0, status=:status WHERE source=:source AND asset=:asset'
                cursor.execute(sql, bindings)
            else:
                status = 'dispenser inexistent'
        else:
            status = 'invalid: status must be one of OPEN or CLOSE'

    if status != 'valid':
        logger.warn("Not storing [dispenser] tx [%s]: %s" %
                    (tx['tx_hash'], status))

    cursor.close()
Example #32
0
def parse (db, tx, message):
    cursor = db.cursor()

    # Unpack message.
    try:
        if len(message) != LENGTH:
            raise exceptions.UnpackError
        assetid, give_quantity, escrow_quantity, mainchainrate, dispenser_status = struct.unpack(FORMAT, message)
        asset = util.generate_asset_name(assetid, util.CURRENT_BLOCK_INDEX)
        status = 'valid'
    except (exceptions.UnpackError, struct.error):
        assetid, give_quantity, mainchainrate, asset = None, None, None, None
        status = 'invalid: could not unpack'

    if status == 'valid':
        assetid, problems = validate (db, tx['source'], asset, give_quantity, escrow_quantity, mainchainrate, dispenser_status, tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)

    if status == 'valid':
        if dispenser_status == STATUS_OPEN:
            cursor.execute('SELECT * FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', {
                'source': tx['source'],
                'asset': asset,
                'status': STATUS_OPEN
            })
            existing = cursor.fetchall()

            if len(existing) == 0:
                # Create the new dispenser
                bindings = {
                    'tx_index': tx['tx_index'],
                    'tx_hash': tx['tx_hash'],
                    'block_index': tx['block_index'],
                    'source': tx['source'],
                    'asset': asset,
                    'give_quantity': give_quantity,
                    'escrow_quantity': escrow_quantity,
                    'satoshirate': mainchainrate,
                    'status': dispenser_status,
                    'give_remaining': escrow_quantity
                }
                try:
                    util.debit(db, tx['source'], asset, escrow_quantity, action='open dispenser', event=tx['tx_hash'])
                    sql = 'insert into dispensers values(:tx_index, :tx_hash, :block_index, :source, :asset, :give_quantity, :escrow_quantity, :satoshirate, :status, :give_remaining)'
                    cursor.execute(sql, bindings)
                except (util.DebitError):
                    status = 'insufficient funds'
            elif len(existing) == 1 and existing[0]['satoshirate'] == mainchainrate and existing[0]['give_quantity'] == give_quantity:
                # Refill the dispenser by the given amount
                bindings = {
                    'source': tx['source'],
                    'asset': asset,
                    'give_remaining': existing[0]['give_remaining'] + escrow_quantity,
                    'status': STATUS_OPEN,
                    'block_index': tx['block_index']
                }
                try:
                    util.debit(db, tx['source'], asset, escrow_quantity, action='refill dispenser', event=tx['tx_hash'])
                    sql = 'UPDATE dispensers SET give_remaining=:give_remaining \
                        WHERE source=:source AND asset=:asset AND status=:status'
                    cursor.execute(sql, bindings)
                except (util.DebitError):
                    status = 'insufficient funds'
            else:
                status = 'can only have one open dispenser per asset per address'
        elif dispenser_status == STATUS_CLOSED:
            cursor.execute('SELECT give_remaining FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', {
                'source': tx['source'],
                'asset': asset,
                'status': STATUS_OPEN
            })
            existing = cursor.fetchall()

            if len(existing) == 1:
                util.credit(db, tx['source'], asset, existing[0]['give_remaining'], action='close dispenser', event=tx['tx_hash'])
                bindings = {
                    'source': tx['source'],
                    'asset': asset,
                    'status': STATUS_CLOSED,
                    'block_index': tx['block_index']
                }
                sql = 'UPDATE dispensers SET give_remaining=0, status=:status WHERE source=:source AND asset=:asset'
                cursor.execute(sql, bindings)
            else:
                status = 'dispenser inexistent'
        else:
            status = 'invalid: status must be one of OPEN or CLOSE'
def parse(db, tx, message):
    cursor = db.cursor()

    # Unpack message.
    try:
        unpacked = unpack(db, message, tx['block_index'])
        asset, quantity, destination, memo_bytes = unpacked['asset'], unpacked[
            'quantity'], unpacked['address'], unpacked['memo']

        status = 'valid'

    except (exceptions.UnpackError, exceptions.AssetNameError,
            struct.error) as e:
        asset, quantity, destination, memo_bytes = None, None, None, None
        status = 'invalid: could not unpack ({})'.format(e)
    except:
        asset, quantity, destination, memo_bytes = None, None, None, None
        status = 'invalid: could not unpack'

    if status == 'valid':
        # don't allow sends over MAX_INT at all
        if quantity and quantity > config.MAX_INT:
            status = 'invalid: quantity is too large'
            quantity = None

    if status == 'valid':
        problems = validate(db, tx['source'], destination, asset, quantity,
                            memo_bytes, tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)

    if status == 'valid':
        # verify balance is present
        cursor.execute(
            '''SELECT * FROM balances \
                                     WHERE (address = ? AND asset = ?)''',
            (tx['source'], asset))
        balances = cursor.fetchall()
        if not balances or balances[0]['quantity'] < quantity:
            status = 'invalid: insufficient funds'

    if status == 'valid':
        util.debit(db,
                   tx['source'],
                   asset,
                   quantity,
                   action='send',
                   event=tx['tx_hash'])
        util.credit(db,
                    destination,
                    asset,
                    quantity,
                    action='send',
                    event=tx['tx_hash'])

    # log invalid transactions
    if status != 'valid':
        if quantity is None:
            logger.warn("Invalid send from %s with status %s. (%s)" %
                        (tx['source'], status, tx['tx_hash']))
        else:
            logger.warn(
                "Invalid send of %s %s from %s to %s. status is %s. (%s)" %
                (quantity, asset, tx['source'], destination, status,
                 tx['tx_hash']))

    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'source': tx['source'],
        'destination': destination,
        'asset': asset,
        'quantity': quantity,
        'status': status,
        'memo': memo_bytes,
    }
    if "integer overflow" not in status and "quantity must be in satoshis" not in status:
        sql = 'insert into sends values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo)'
        cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [send] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    cursor.close()
def parse (db, tx, message, message_type_id):
    issuance_parse_cursor = db.cursor()

    # Unpack message.
    try:
        subasset_longname = None
        if message_type_id == SUBASSET_ID:
            if not util.enabled('subassets', block_index=tx['block_index']):
                logger.warn("subassets are not enabled at block %s" % tx['block_index'])
                raise exceptions.UnpackError

            # parse a subasset original issuance message
            asset_id, quantity, divisible, compacted_subasset_length = struct.unpack(SUBASSET_FORMAT, message[0:SUBASSET_FORMAT_LENGTH])
            description_length = len(message) - SUBASSET_FORMAT_LENGTH - compacted_subasset_length
            if description_length < 0:
                logger.warn("invalid subasset length: [issuance] tx [%s]: %s" % (tx['tx_hash'], compacted_subasset_length))
                raise exceptions.UnpackError
            messages_format = '>{}s{}s'.format(compacted_subasset_length, description_length)
            compacted_subasset_longname, description = struct.unpack(messages_format, message[SUBASSET_FORMAT_LENGTH:])
            subasset_longname = util.expand_subasset_longname(compacted_subasset_longname)
            callable_, call_date, call_price = False, 0, 0.0
            try:
                description = description.decode('utf-8')
            except UnicodeDecodeError:
                description = ''
        elif (tx['block_index'] > 283271 or config.TESTNET or config.REGTEST) and len(message) >= LENGTH_2: # Protocol change.
            if len(message) - LENGTH_2 <= 42:
                curr_format = FORMAT_2 + '{}p'.format(len(message) - LENGTH_2)
            else:
                curr_format = FORMAT_2 + '{}s'.format(len(message) - LENGTH_2)
            asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(curr_format, message)

            call_price = round(call_price, 6) # TODO: arbitrary
            try:
                description = description.decode('utf-8')
            except UnicodeDecodeError:
                description = ''
        else:
            if len(message) != LENGTH_1:
                raise exceptions.UnpackError
            asset_id, quantity, divisible = struct.unpack(FORMAT_1, message)
            callable_, call_date, call_price, description = False, 0, 0.0, ''
        try:
            asset = util.generate_asset_name(asset_id, tx['block_index'])
            status = 'valid'
        except exceptions.AssetIDError:
            asset = None
            status = 'invalid: bad asset name'
    except exceptions.UnpackError as e:
        asset, quantity, divisible, callable_, call_date, call_price, description = None, None, None, None, None, None, None
        status = 'invalid: could not unpack'

    # parse and validate the subasset from the message
    subasset_parent = None
    if status == 'valid' and subasset_longname is not None: # Protocol change.
        try:
            # ensure the subasset_longname is valid
            util.validate_subasset_longname(subasset_longname)
            subasset_parent, subasset_longname = util.parse_subasset_from_asset_name(subasset_longname)
        except exceptions.AssetNameError as e:
            asset = None
            status = 'invalid: bad subasset name'

    reissuance = None
    fee = 0
    if status == 'valid':
        call_date, call_price, problems, fee, description, divisible, reissuance, reissued_asset_longname = validate(db, tx['source'], tx['destination'], asset, quantity, divisible, callable_, call_date, call_price, description, subasset_parent, subasset_longname, block_index=tx['block_index'])

        if problems: status = 'invalid: ' + '; '.join(problems)
        if not util.enabled('integer_overflow_fix', block_index=tx['block_index']) and 'total quantity overflow' in problems:
            quantity = 0

    if tx['destination']:
        issuer = tx['destination']
        transfer = True
        quantity = 0
    else:
        issuer = tx['source']
        transfer = False

    # Debit fee.
    if status == 'valid':
        util.debit(db, tx['source'], config.XCP, fee, action="issuance fee", event=tx['tx_hash'])

    # Lock?
    lock = False
    if status == 'valid':
        if description and description.lower() == 'lock':
            lock = True
            cursor = db.cursor()
            issuances = list(cursor.execute('''SELECT * FROM issuances \
                                               WHERE (status = ? AND asset = ?)
                                               ORDER BY tx_index ASC''', ('valid', asset)))
            cursor.close()
            description = issuances[-1]['description']  # Use last description. (Assume previous issuance exists because tx is valid.)
            timestamp, value_int, fee_fraction_int = None, None, None

        if not reissuance:
            # Add to table of assets.
            bindings= {
                'asset_id': str(asset_id),
                'asset_name': str(asset),
                'block_index': tx['block_index'],
                'asset_longname': subasset_longname,
            }
            sql='insert into assets values(:asset_id, :asset_name, :block_index, :asset_longname)'
            issuance_parse_cursor.execute(sql, bindings)

    if status == 'valid' and reissuance:
        # when reissuing, add the asset_longname to the issuances table for API lookups
        asset_longname = reissued_asset_longname
    else:
        asset_longname = subasset_longname

    # Add parsed transaction to message-type–specific table.
    bindings= {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'asset': asset,
        'quantity': quantity,
        'divisible': divisible,
        'source': tx['source'],
        'issuer': issuer,
        'transfer': transfer,
        'callable': callable_,
        'call_date': call_date,
        'call_price': call_price,
        'description': description,
        'fee_paid': fee,
        'locked': lock,
        'status': status,
        'asset_longname': asset_longname,
    }
    if "integer overflow" not in status:
        sql='insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname)'
        issuance_parse_cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [issuance] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    # Credit.
    if status == 'valid' and quantity:
        util.credit(db, tx['source'], asset, quantity, action="issuance", event=tx['tx_hash'])

    issuance_parse_cursor.close()
Example #35
0
def parse(db, tx, message, message_type_id):
    issuance_parse_cursor = db.cursor()

    # Unpack message.
    try:
        subasset_longname = None
        if message_type_id == SUBASSET_ID:
            if not util.enabled('subassets', block_index=tx['block_index']):
                logger.warn("subassets are not enabled at block %s" %
                            tx['block_index'])
                raise exceptions.UnpackError

            # parse a subasset original issuance message
            asset_id, quantity, divisible, compacted_subasset_length = struct.unpack(
                SUBASSET_FORMAT, message[0:SUBASSET_FORMAT_LENGTH])
            description_length = len(
                message) - SUBASSET_FORMAT_LENGTH - compacted_subasset_length
            if description_length < 0:
                logger.warn("invalid subasset length: [issuance] tx [%s]: %s" %
                            (tx['tx_hash'], compacted_subasset_length))
                raise exceptions.UnpackError
            messages_format = '>{}s{}s'.format(compacted_subasset_length,
                                               description_length)
            compacted_subasset_longname, description = struct.unpack(
                messages_format, message[SUBASSET_FORMAT_LENGTH:])
            subasset_longname = util.expand_subasset_longname(
                compacted_subasset_longname)
            callable_, call_date, call_price = False, 0, 0.0
            try:
                description = description.decode('utf-8')
            except UnicodeDecodeError:
                description = ''
        elif (tx['block_index'] > 283271 or
              config.TESTNET) and len(message) >= LENGTH_2:  # Protocol change.
            if len(message) - LENGTH_2 <= 42:
                curr_format = FORMAT_2 + '{}p'.format(len(message) - LENGTH_2)
            else:
                curr_format = FORMAT_2 + '{}s'.format(len(message) - LENGTH_2)
            asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(
                curr_format, message)

            call_price = round(call_price, 6)  # TODO: arbitrary
            try:
                description = description.decode('utf-8')
            except UnicodeDecodeError:
                description = ''
        else:
            if len(message) != LENGTH_1:
                raise exceptions.UnpackError
            asset_id, quantity, divisible = struct.unpack(FORMAT_1, message)
            callable_, call_date, call_price, description = False, 0, 0.0, ''
        try:
            asset = util.generate_asset_name(asset_id, tx['block_index'])
        except exceptions.AssetNameError:
            asset = None
            status = 'invalid: bad asset name'
        status = 'valid'
    except exceptions.UnpackError as e:
        asset, quantity, divisible, callable_, call_date, call_price, description = None, None, None, None, None, None, None
        status = 'invalid: could not unpack'

    # parse and validate the subasset from the message
    subasset_parent = None
    if status == 'valid' and subasset_longname is not None:  # Protocol change.
        try:
            # ensure the subasset_longname is valid
            util.validate_subasset_longname(subasset_longname)
            subasset_parent, subasset_longname = util.parse_subasset_from_asset_name(
                subasset_longname)
        except exceptions.AssetNameError as e:
            asset = None
            status = 'invalid: bad subasset name'

    reissuance = None
    fee = 0
    if status == 'valid':
        call_date, call_price, problems, fee, description, divisible, reissuance, reissued_asset_longname = validate(
            db,
            tx['source'],
            tx['destination'],
            asset,
            quantity,
            divisible,
            callable_,
            call_date,
            call_price,
            description,
            subasset_parent,
            subasset_longname,
            block_index=tx['block_index'])

        if problems: status = 'invalid: ' + '; '.join(problems)
        if not util.enabled('integer_overflow_fix',
                            block_index=tx['block_index']
                            ) and 'total quantity overflow' in problems:
            quantity = 0

    if tx['destination']:
        issuer = tx['destination']
        transfer = True
        quantity = 0
    else:
        issuer = tx['source']
        transfer = False

    # Debit fee.
    if status == 'valid':
        util.debit(db,
                   tx['source'],
                   config.XCP,
                   fee,
                   action="issuance fee",
                   event=tx['tx_hash'])

    # Lock?
    lock = False
    if status == 'valid':
        if description and description.lower() == 'lock':
            lock = True
            cursor = db.cursor()
            issuances = list(
                cursor.execute(
                    '''SELECT * FROM issuances \
                                               WHERE (status = ? AND asset = ?)
                                               ORDER BY tx_index ASC''',
                    ('valid', asset)))
            cursor.close()
            description = issuances[-1][
                'description']  # Use last description. (Assume previous issuance exists because tx is valid.)
            timestamp, value_int, fee_fraction_int = None, None, None

        if not reissuance:
            # Add to table of assets.
            bindings = {
                'asset_id': str(asset_id),
                'asset_name': str(asset),
                'block_index': tx['block_index'],
                'asset_longname': subasset_longname,
            }
            sql = 'insert into assets values(:asset_id, :asset_name, :block_index, :asset_longname)'
            issuance_parse_cursor.execute(sql, bindings)

    if status == 'valid' and reissuance:
        # when reissuing, add the asset_longname to the issuances table for API lookups
        asset_longname = reissued_asset_longname
    else:
        asset_longname = subasset_longname

    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'asset': asset,
        'quantity': quantity,
        'divisible': divisible,
        'source': tx['source'],
        'issuer': issuer,
        'transfer': transfer,
        'callable': callable_,
        'call_date': call_date,
        'call_price': call_price,
        'description': description,
        'fee_paid': fee,
        'locked': lock,
        'status': status,
        'asset_longname': asset_longname,
    }
    if "integer overflow" not in status:
        sql = 'insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname)'
        issuance_parse_cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [issuance] tx [%s]: %s" %
                    (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    # Credit.
    if status == 'valid' and quantity:
        util.credit(db,
                    tx['source'],
                    asset,
                    quantity,
                    action="issuance",
                    event=tx['tx_hash'])

    issuance_parse_cursor.close()
Example #36
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,
                                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,
                                bear_address,
                                config.XCP,
                                bear_credit,
                                action='bet {}'.format(bet_match_status),
                                event=tx['tx_hash'])

                # Pay fee to feed.
                util.credit(db,
                            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,
                            bull_address,
                            config.XCP,
                            bull_credit,
                            action='bet {}'.format(bet_match_status),
                            event=tx['tx_hash'])
                util.credit(db,
                            bear_address,
                            config.XCP,
                            bear_credit,
                            action='bet {}'.format(bet_match_status),
                            event=tx['tx_hash'])

                # Pay fee to feed.
                util.credit(db,
                            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,
                            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,
                            notequal_address,
                            config.XCP,
                            escrow_less_fee,
                            action='bet {}'.format(bet_match_status),
                            event=tx['tx_hash'])

            # Pay fee to feed.
            util.credit(db,
                        bet_match['feed_address'],
                        config.XCP,
                        fee,
                        action='feed fee',
                        event=tx['tx_hash'])

            # For logging purposes.
            bindings = {
                'bet_match_id': bet_match_id,
                'bet_match_type_id': bet_match_type_id,
                'block_index': tx['block_index'],
                'settled': None,
                'bull_credit': None,
                'bear_credit': None,
                'winner': winner,
                'escrow_less_fee': escrow_less_fee,
                'fee': fee
            }
            sql = 'insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)'
            cursor.execute(sql, bindings)

        # Update the bet match’s status.
        if bet_match_status:
            bindings = {
                'status':
                bet_match_status,
                'bet_match_id':
                util.make_id(bet_match['tx0_hash'], bet_match['tx1_hash'])
            }
            sql = 'update bet_matches set status = :status where id = :bet_match_id'
            cursor.execute(sql, bindings)
            log.message(db, tx['block_index'], 'update', 'bet_matches',
                        bindings)

        broadcast_bet_match_cursor.close()

    cursor.close()
def parse(db, tx, message):
    cursor = db.cursor()

    # Unpack message.
    try:
        if util.enabled("broadcast_pack_text"):
            timestamp, value, fee_fraction_int, rawtext = struct.unpack(
                FORMAT + "{}s".format(len(message) - LENGTH), message
            )
            textlen = VarIntSerializer.deserialize(rawtext)
            text = rawtext[-textlen:]

            assert len(text) == textlen
        else:
            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,
    }
    if "integer overflow" not in 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)
    else:
        logger.warn("Not storing [broadcast] tx [%s]: %s" % (tx["tx_hash"], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings),))

    # stop processing if broadcast is invalid for any reason
    if util.enabled("broadcast_invalid_check") and status != "valid":
        return

    # Negative values (default to ignore).
    if value is 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

    # stop processing if broadcast is invalid for any reason
    # @TODO: remove this check once broadcast_invalid_check has been activated
    if util.enabled("max_fee_fraction") and status != "valid":
        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,
                        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,
                        bear_address,
                        config.XCP,
                        bear_credit,
                        action="bet {}".format(bet_match_status),
                        event=tx["tx_hash"],
                    )

                # Pay fee to feed.
                util.credit(db, 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,
                    bull_address,
                    config.XCP,
                    bull_credit,
                    action="bet {}".format(bet_match_status),
                    event=tx["tx_hash"],
                )
                util.credit(
                    db,
                    bear_address,
                    config.XCP,
                    bear_credit,
                    action="bet {}".format(bet_match_status),
                    event=tx["tx_hash"],
                )

                # Pay fee to feed.
                util.credit(db, 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,
                    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,
                    notequal_address,
                    config.XCP,
                    escrow_less_fee,
                    action="bet {}".format(bet_match_status),
                    event=tx["tx_hash"],
                )

            # Pay fee to feed.
            util.credit(db, 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()
Example #38
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):
        tx0_hash, tx1_hash, order_match_id = None, None, None
        status = 'invalid: could not unpack'

    if status == 'valid':
        _, btc_quantity, escrowed_asset, escrowed_quantity, _, problems = validate(
            db, tx['source'], order_match_id, tx['block_index'])
        if problems:
            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['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)

            # Update give and get order status as filled if order_match is completed
            if util.enabled('btc_order_filled'):
                bindings = {
                    'status': 'pending',
                    'tx0_hash': tx0_hash,
                    'tx1_hash': tx1_hash
                }
                sql = 'select * from order_matches where status = :status and ((tx0_hash in (:tx0_hash, :tx1_hash)) or ((tx1_hash in (:tx0_hash, :tx1_hash))))'
                cursor.execute(sql, bindings)
                order_matches = cursor.fetchall()
                if len(order_matches) == 0:
                    # mark both btc get and give orders as filled when order_match is completed and give or get remaining = 0
                    bindings = {
                        'status': 'filled',
                        'tx0_hash': tx0_hash,
                        'tx1_hash': tx1_hash
                    }
                    sql = 'update orders set status = :status where ((tx_hash in (:tx0_hash, :tx1_hash)) and ((give_remaining = 0) or (get_remaining = 0)))'
                    cursor.execute(sql, bindings)
                else:
                    # always mark btc get order as filled when order_match is completed and give or get remaining = 0
                    bindings = {
                        'status': 'filled',
                        'source': tx['destination'],
                        'tx0_hash': tx0_hash,
                        'tx1_hash': tx1_hash
                    }
                    sql = 'update orders set status = :status where ((tx_hash in (:tx0_hash, :tx1_hash)) and ((give_remaining = 0) or (get_remaining = 0)) and (source = :source))'
                    cursor.execute(sql, bindings)

    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'source': tx['source'],
        'destination': tx['destination'],
        'btc_amount': tx['btc_amount'],
        'order_match_id': order_match_id,
        'status': status,
    }
    if "integer overflow" not in status:
        sql = 'insert into btcpays values(:tx_index, :tx_hash, :block_index, :source, :destination, :btc_amount, :order_match_id, :status)'
        cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [btcpay] tx [%s]: %s" %
                    (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))
Example #39
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, 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, 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()
Example #40
0
def parse (db, tx, message):
    dividend_parse_cursor = db.cursor()

    # Unpack message.
    try:
        if (tx['block_index'] > 288150 or config.TESTNET or config.REGTEST) and len(message) == LENGTH_2:
            quantity_per_unit, asset_id, dividend_asset_id = struct.unpack(FORMAT_2, message)
            asset = util.get_asset_name(db, asset_id, tx['block_index'])
            dividend_asset = util.get_asset_name(db, dividend_asset_id, tx['block_index'])
            status = 'valid'
        elif len(message) == LENGTH_1:
            quantity_per_unit, asset_id = struct.unpack(FORMAT_1, message)
            asset = util.get_asset_name(db, asset_id, tx['block_index'])
            dividend_asset = config.XCP
            status = 'valid'
        else:
            raise exceptions.UnpackError
    except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e:
        dividend_asset, quantity_per_unit, asset = None, None, None
        status = 'invalid: could not unpack'

    if dividend_asset == config.BTC:
        status = 'invalid: cannot pay {} dividends within protocol'.format(config.BTC)

    if status == 'valid':
        # For SQLite3
        quantity_per_unit = min(quantity_per_unit, config.MAX_INT)

        dividend_total, outputs, problems, fee = validate(db, tx['source'], quantity_per_unit, asset, dividend_asset, block_index=tx['block_index'])
        if problems: status = 'invalid: ' + '; '.join(problems)

    if status == 'valid':
        # Debit.
        util.debit(db, tx['source'], dividend_asset, dividend_total, action='dividend', event=tx['tx_hash'])
        if tx['block_index'] >= 330000 or config.TESTNET or config.REGTEST: # Protocol change.
            util.debit(db, tx['source'], config.XCP, fee, action='dividend fee', event=tx['tx_hash'])

        # Credit.
        for output in outputs:
            if not util.enabled('dont_credit_zero_dividend') or output['dividend_quantity'] > 0:
                util.credit(db, output['address'], dividend_asset, output['dividend_quantity'], action='dividend', event=tx['tx_hash'])

    # Add parsed transaction to message-type–specific table.
    bindings = {
        'tx_index': tx['tx_index'],
        'tx_hash': tx['tx_hash'],
        'block_index': tx['block_index'],
        'source': tx['source'],
        'asset': asset,
        'dividend_asset': dividend_asset,
        'quantity_per_unit': quantity_per_unit,
        'fee_paid': fee,
        'status': status,
    }

    if "integer overflow" not in status:
        sql = 'insert into dividends values(:tx_index, :tx_hash, :block_index, :source, :asset, :dividend_asset, :quantity_per_unit, :fee_paid, :status)'
        dividend_parse_cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [dividend] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    dividend_parse_cursor.close()
Example #41
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():
        if util.enabled('max_fee_fraction'):
            if status != 'valid':
                break

        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, 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, bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash'])

                # Pay fee to feed.
                util.credit(db, 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, bull_address, config.XCP, bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash'])
                util.credit(db, bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash'])

                # Pay fee to feed.
                util.credit(db, 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, 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, notequal_address, config.XCP, escrow_less_fee, action='bet {}'.format(bet_match_status), event=tx['tx_hash'])

            # Pay fee to feed.
            util.credit(db, 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()
Example #42
0
def parse(db, tx, MAINNET_BURNS, message=None):
    burn_parse_cursor = db.cursor()

    if config.TESTNET or config.REGTEST:
        problems = []
        status = 'valid'

        if status == 'valid':
            problems = validate(db,
                                tx['source'],
                                tx['destination'],
                                tx['btc_amount'],
                                tx['block_index'],
                                overburn=False)
            if problems: status = 'invalid: ' + '; '.join(problems)

            if tx['btc_amount'] != None:
                sent = tx['btc_amount']
            else:
                sent = 0

        if status == 'valid':
            # Calculate quantity of XCP earned. (Maximum 1 BTC in total, ever.)
            cursor = db.cursor()
            cursor.execute(
                '''SELECT * FROM burns WHERE (status = ? AND source = ?)''',
                ('valid', tx['source']))
            burns = cursor.fetchall()
            already_burned = sum([burn['burned'] for burn in burns])
            ONE = 1 * config.UNIT
            max_burn = ONE - already_burned
            if sent > max_burn:
                burned = max_burn  # Exceeded maximum burn; earn what you can.
            else:
                burned = sent

            total_time = config.BURN_END - config.BURN_START
            partial_time = config.BURN_END - tx['block_index']
            multiplier = (1000 + (500 * Fraction(partial_time, total_time)))
            earned = round(burned * multiplier)

            # Credit source address with earned XCP.
            util.credit(db,
                        tx['source'],
                        config.XCP,
                        earned,
                        action='burn',
                        event=tx['tx_hash'])
        else:
            burned = 0
            earned = 0

        tx_index = tx['tx_index']
        tx_hash = tx['tx_hash']
        block_index = tx['block_index']
        source = tx['source']

    else:
        # Mainnet burns are hard‐coded.

        try:
            line = MAINNET_BURNS[tx['tx_hash']]
        except KeyError:
            return

        util.credit(db,
                    line['source'],
                    config.XCP,
                    int(line['earned']),
                    action='burn',
                    event=line['tx_hash'])

        tx_index = tx['tx_index']
        tx_hash = line['tx_hash']
        block_index = line['block_index']
        source = line['source']
        burned = line['burned']
        earned = line['earned']
        status = 'valid'

    # Add parsed transaction to message-type–specific table.
    # TODO: store sent in table
    bindings = {
        'tx_index': tx_index,
        'tx_hash': tx_hash,
        'block_index': block_index,
        'source': source,
        'burned': burned,
        'earned': earned,
        'status': status,
    }
    if "integer overflow" not in status:
        sql = 'insert into burns values(:tx_index, :tx_hash, :block_index, :source, :burned, :earned, :status)'
        burn_parse_cursor.execute(sql, bindings)
    else:
        logger.warn("Not storing [burn] tx [%s]: %s" % (tx['tx_hash'], status))
        logger.debug("Bindings: %s" % (json.dumps(bindings), ))

    burn_parse_cursor.close()
Example #43
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 or config.REGTEST:  # 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']))  # 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'])
        tx0_inverse_odds = util.price(tx0['counterwager_quantity'],
                                      tx0['wager_quantity'])
        tx1_odds = util.price(tx1['wager_quantity'],
                              tx1['counterwager_quantity'])

        if tx['block_index'] < 286000:
            tx0_inverse_odds = util.price(1, tx0_odds)  # 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))))
            forward_quantity = int(
                min(tx0_wager_remaining,
                    int(util.price(tx1_wager_remaining, tx1_odds))))
            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 or config.REGTEST:  # 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,
                            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, tx['block_index'], 'update', 'bets', bindings)

            if tx1['block_index'] >= 292000 or config.TESTNET or config.REGTEST:  # 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['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, tx['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
Example #44
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']))   # 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'])
        tx0_inverse_odds = util.price(tx0['counterwager_quantity'], tx0['wager_quantity'])
        tx1_odds = util.price(tx1['wager_quantity'], tx1['counterwager_quantity'])

        if tx['block_index'] < 286000: tx0_inverse_odds = util.price(1, tx0_odds) # 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))))
            forward_quantity = int(min(tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds))))
            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, 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, tx['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['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, tx['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