Ejemplo n.º 1
0
def compose(db, source, give_asset, give_quantity, get_asset, get_quantity,
            expiration, fee_required):
    cursor = db.cursor()

    # resolve subassets
    give_asset = util.resolve_subasset_longname(db, give_asset)
    get_asset = util.resolve_subasset_longname(db, get_asset)

    # Check balance.
    if give_asset != config.BTC:
        balances = list(
            cursor.execute(
                '''SELECT * FROM balances WHERE (address = ? AND asset = ?)''',
                (source, give_asset)))
        if (not balances or balances[0]['quantity'] < give_quantity):
            raise exceptions.ComposeError('insufficient funds')

    problems = validate(db, source, give_asset, give_quantity, get_asset,
                        get_quantity, expiration, fee_required,
                        util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    give_id = util.get_asset_id(db, give_asset, util.CURRENT_BLOCK_INDEX)
    get_id = util.get_asset_id(db, get_asset, util.CURRENT_BLOCK_INDEX)
    data = message_type.pack(ID)
    data += struct.pack(FORMAT, give_id, give_quantity, get_id, get_quantity,
                        expiration, fee_required)
    cursor.close()
    return (source, [], data)
Ejemplo n.º 2
0
def compose(db, source, asset_dest_quant_list, memo, memo_is_hex):
    cursor = db.cursor()

    out_balances = util.accumulate([(t[0], t[2])
                                    for t in asset_dest_quant_list])
    for (asset, quantity) in out_balances:

        # resolve subassets
        asset = util.resolve_subasset_longname(db, asset)

        if not isinstance(quantity, int):
            raise exceptions.ComposeError(
                'quantities must be an int (in satoshis) for {}'.format(asset))

        balances = list(
            cursor.execute(
                '''SELECT * FROM balances WHERE (address = ? AND asset = ?)''',
                (source, asset)))
        if not balances or balances[0]['quantity'] < quantity:
            raise exceptions.ComposeError(
                'insufficient funds for {}'.format(asset))

    block_index = util.CURRENT_BLOCK_INDEX

    problems = validate(db, source, asset_dest_quant_list, block_index)
    if problems: raise exceptions.ComposeError(problems)

    data = message_type.pack(ID)
    data += _encode_mpmaSend(db,
                             asset_dest_quant_list,
                             block_index,
                             memo=memo,
                             memo_is_hex=memo_is_hex)

    return (source, [], data)
Ejemplo n.º 3
0
def is_vendable(db, asset):
    if asset == config.XCP:
        return True  # Always vendable.

    asset = util.resolve_subasset_longname(db, asset)
    cursor = db.cursor()
    issuances = list(
        cursor.execute(
            '''SELECT vendable, reassignable, listed FROM issuances
                                               WHERE (status = ? AND asset = ?)
                                               ORDER BY tx_index DESC LIMIT 1''',
            ('valid', asset)))
    if (len(issuances) <= 0):
        return False

    vendable = issuances[0]['vendable']  # Use the last issuance.
    reassignable = issuances[0]['reassignable']
    listed = issuances[0]['listed']

    if not util.enabled('dispensers'):
        return False
    elif not util.enabled('enable_vendable_fix') and (reassignable == False
                                                      or listed == False):
        return False
    else:
        return vendable
Ejemplo n.º 4
0
 def get_holder_count(asset):
     asset = util.resolve_subasset_longname(self.db, asset)
     holders = util.holders(self.db, asset, True)
     addresses = []
     for holder in holders:
         addresses.append(holder['address'])
     return {asset: len(set(addresses))}
Ejemplo n.º 5
0
def compose (db, source, quantity_per_unit, asset, dividend_asset):
    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)
    dividend_asset = util.resolve_subasset_longname(db, dividend_asset)

    dividend_total, outputs, problems, fee = validate(db, source, quantity_per_unit, asset, dividend_asset, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)
    logger.info('Total quantity to be distributed in dividends: {} {}'.format(util.value_out(db, dividend_total, dividend_asset), dividend_asset))

    if dividend_asset == config.BTC:
        return (source, [(output['address'], output['dividend_quantity']) for output in outputs], None)

    asset_id = util.get_asset_id(db, asset, util.CURRENT_BLOCK_INDEX)
    dividend_asset_id = util.get_asset_id(db, dividend_asset, util.CURRENT_BLOCK_INDEX)
    data = message_type.pack(ID)
    data += struct.pack(FORMAT_2, quantity_per_unit, asset_id, dividend_asset_id)
    return (source, [], data)
Ejemplo n.º 6
0
def compose(db, source, asset, quantity, tag):
    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)

    validate(db, source, None, asset, quantity)
    data = pack(db, asset, quantity, tag)

    return (source, [], data)
Ejemplo n.º 7
0
def compose (db, source, asset, quantity, tag):
    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)

    validate(db, source, None, asset, quantity)
    data = pack(db, asset, quantity, tag)

    return (source, [], data)
Ejemplo n.º 8
0
 def get_supply(asset):
     if asset == config.BTC:
         return backend.get_btc_supply(normalize=False)
     elif asset == config.XCP:
         return util.xcp_supply(self.db)
     else:
         asset = util.resolve_subasset_longname(self.db, asset)
         return util.asset_supply(self.db, asset)
Ejemplo n.º 9
0
def compose (db, source, quantity_per_unit, asset, dividend_asset):
    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)
    dividend_asset = util.resolve_subasset_longname(db, dividend_asset)

    dividend_total, outputs, problems, fee = validate(db, source, quantity_per_unit, asset, dividend_asset, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)
    logger.info('Total quantity to be distributed in dividends: {} {}'.format(util.value_out(db, dividend_total, dividend_asset), dividend_asset))

    if dividend_asset == config.BTC:
        return (source, [(output['address'], output['dividend_quantity']) for output in outputs], None)

    asset_id = util.get_asset_id(db, asset, util.CURRENT_BLOCK_INDEX)
    dividend_asset_id = util.get_asset_id(db, dividend_asset, util.CURRENT_BLOCK_INDEX)
    data = message_type.pack(ID)
    data += struct.pack(FORMAT_2, quantity_per_unit, asset_id, dividend_asset_id)
    return (source, [], data)
Ejemplo n.º 10
0
 def get_supply(asset):
     if asset == 'BTC':
         return backend.get_btc_supply(normalize=False)
     elif asset == 'XCP':
         return util.xcp_supply(db)
     else:
         asset = util.resolve_subasset_longname(db, asset)
         return util.asset_supply(db, asset)
Ejemplo n.º 11
0
def validate (db, source, asset_, give_quantity, escrow_quantity, mainchainrate, status, block_index):
    problems = []
    asset_id = None

    if not util.enabled('dispensers'):
        problems.append('not activated yet.')

    if asset_ == config.BTC:
        problems.append('cannot dispense %s' % config.BTC)
        return None, problems

    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset_)

    if status == STATUS_OPEN:
        if not issuance.is_vendable(db, asset):
            problems.append('asset "%s" is not vendable' % asset_)
        if give_quantity <= 0: problems.append('give_quantity must be positive')
        if mainchainrate <= 0: problems.append('mainchainrate must be positive')
        if escrow_quantity < give_quantity:
            problems.append('escrow_quantity must be greater or equal than give_quantity')
    elif not(status == STATUS_CLOSED):
        problems.append('invalid status %i' % status)

    cursor = db.cursor()
    cursor.execute('''SELECT quantity FROM balances \
                      WHERE address = ? and asset = ?''', (source,asset,))
    available = cursor.fetchall()

    if len(available) == 0:
        problems.append('address doesn\'t has the asset %s' % asset_)
    elif len(available) >= 1 and available[0]['quantity'] < escrow_quantity:
        problems.append('address doesn\'t has enough balance of %s (%i < %i)' % (asset_, available[0]['quantity'], escrow_quantity))
    else:
        cursor.execute('''SELECT * FROM dispensers WHERE source = ? AND asset = ? AND status=?''', (source, asset, STATUS_OPEN))
        open_dispensers = cursor.fetchall()
        if status == STATUS_OPEN:
            if len(open_dispensers) > 0 and open_dispensers[0]['satoshirate'] != mainchainrate:
                problems.append('address has a dispenser already opened for asset %s with a different mainchainrate' % asset_)

            if len(open_dispensers) > 0 and open_dispensers[0]['give_quantity'] != give_quantity:
                problems.append('address has a dispenser already opened for asset %s with a different give_quantity' % asset_)
        elif status == STATUS_CLOSED:
            if len(open_dispensers) == 0:
                problems.append('address doesnt has an open dispenser for asset %s' % asset_)

        if len(problems) == 0:
            asset_id = util.generate_asset_id(asset, block_index)
            if asset_id == 0:
                problems.append('cannot dispense %s' % asset_) # How can we test this on a test vector?

    if give_quantity > config.MAX_INT or escrow_quantity > config.MAX_INT or mainchainrate > config.MAX_INT:
        problems.append('integer overflow')

    if len(problems) > 0:
        return None, problems
    else:
        return asset_id, None
Ejemplo n.º 12
0
        def get_asset_info(assets):
            logger.warning("Deprecated method: `get_asset_info`")
            if not isinstance(assets, list):
                raise APIError("assets must be a list of asset names, even if it just contains one entry")
            assetsInfo = []
            for asset in assets:
                asset = util.resolve_subasset_longname(self.db, asset)

                # BTC and XCP.
                if asset in [config.BTC, config.XCP]:
                    if asset == config.BTC:
                        supply = backend.get_btc_supply(normalize=False)
                    else:
                        supply = util.xcp_supply(self.db)

                    assetsInfo.append({
                        'asset': asset,
                        'asset_longname': None,
                        'owner': None,
                        'divisible': True,
                        'listed': True,
                        'reassignable': True,
                        'vendable': False if asset == config.BTC else True,
                        'locked': False,
                        'supply': supply,
                        'description': '',
                        'issuer': None
                    })
                    continue

                # User‐created asset.
                cursor = self.db.cursor()
                issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset)))
                if not issuances:
                    continue #asset not found, most likely
                else:
                    last_issuance = issuances[-1]
                locked = False
                for e in issuances:
                    if e['locked']: locked = True
                assetsInfo.append({
                    'asset': asset,
                    'asset_longname': last_issuance['asset_longname'],
                    'owner': last_issuance['issuer'],
                    'divisible': bool(last_issuance['divisible']),
                    'listed': bool(last_issuance['listed']),
                    'reassignable': bool(last_issuance['reassignable']),
                    'vendable': bool(last_issuance['vendable']),
                    'locked': locked,
                    'supply': util.asset_supply(self.db, asset),
                    'description': last_issuance['description'],
                    'issuer': last_issuance['issuer']})
            return assetsInfo
Ejemplo n.º 13
0
def compose (db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required):
    cursor = db.cursor()

    # resolve subassets
    give_asset = util.resolve_subasset_longname(db, give_asset)
    get_asset = util.resolve_subasset_longname(db, get_asset)

    # Check balance.
    if give_asset != config.BTC:
        balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, give_asset)))
        if (not balances or balances[0]['quantity'] < give_quantity):
            raise exceptions.ComposeError('insufficient funds')

    problems = validate(db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    give_id = util.get_asset_id(db, give_asset, util.CURRENT_BLOCK_INDEX)
    get_id = util.get_asset_id(db, get_asset, util.CURRENT_BLOCK_INDEX)
    data = message_type.pack(ID)
    data += struct.pack(FORMAT, give_id, give_quantity, get_id, get_quantity,
                        expiration, fee_required)
    cursor.close()
    return (source, [], data)
Ejemplo n.º 14
0
def compose(db, source, destination, asset, quantity, memo, memo_is_hex):
    cursor = db.cursor()

    # Just send BTC?
    if asset == config.BTC:
        return (source, [(destination, quantity)], None)

    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)

    #quantity must be in int satoshi (not float, string, etc)
    if not isinstance(quantity, int):
        raise exceptions.ComposeError('quantity must be an int (in satoshi)')

    # Only for outgoing (incoming will overburn).
    balances = list(
        cursor.execute(
            '''SELECT * FROM balances WHERE (address = ? AND asset = ?)''',
            (source, asset)))
    if not balances or balances[0]['quantity'] < quantity:
        raise exceptions.ComposeError('insufficient funds')

    # convert memo to memo_bytes based on memo_is_hex setting
    if memo is None:
        memo_bytes = b''
    elif memo_is_hex:
        memo_bytes = bytes.fromhex(memo)
    else:
        memo = memo.encode('utf-8')
        memo_bytes = struct.pack(">{}s".format(len(memo)), memo)

    block_index = util.CURRENT_BLOCK_INDEX

    problems = validate(db, source, destination, asset, quantity, memo_bytes,
                        block_index)
    if problems: raise exceptions.ComposeError(problems)

    asset_id = util.get_asset_id(db, asset, block_index)

    short_address_bytes = address.pack(destination)

    data = message_type.pack(ID)
    data += struct.pack(FORMAT, asset_id, quantity, short_address_bytes)
    data += memo_bytes

    cursor.close()
    # return an empty array as the second argument because we don't need to send BTC dust to the recipient
    return (source, [], data)
def compose (db, source, destination, asset, quantity, memo, memo_is_hex):
    cursor = db.cursor()

    # Just send BTC?
    if asset == config.BTC:
        return (source, [(destination, quantity)], None)

    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)

    #quantity must be in int satoshi (not float, string, etc)
    if not isinstance(quantity, int):
        raise exceptions.ComposeError('quantity must be an int (in satoshi)')

    # Only for outgoing (incoming will overburn).
    balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, asset)))
    if not balances or balances[0]['quantity'] < quantity:
        raise exceptions.ComposeError('insufficient funds')

    # convert memo to memo_bytes based on memo_is_hex setting
    if memo is None:
        memo_bytes = b''
    elif memo_is_hex:
        memo_bytes = bytes.fromhex(memo)
    else:
        memo = memo.encode('utf-8')
        memo_bytes = struct.pack(">{}s".format(len(memo)), memo)

    block_index = util.CURRENT_BLOCK_INDEX

    problems = validate(db, source, destination, asset, quantity, memo_bytes, block_index)
    if problems: raise exceptions.ComposeError(problems)

    asset_id = util.get_asset_id(db, asset, block_index)

    short_address_bytes = address.pack(destination)

    data = message_type.pack(ID)
    data += struct.pack(FORMAT, asset_id, quantity, short_address_bytes)
    data += memo_bytes

    cursor.close()
    # return an empty array as the second argument because we don't need to send BTC dust to the recipient
    return (source, [], data)
Ejemplo n.º 16
0
def compose(db, source, asset, quantity, tag):
    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)

    validate(db, source, None, asset, quantity)

    # Validations only required in the compose phase.
    try:
        bytes.fromhex(tag)
    except ValueError:
        raise ValidateError('tag is not a hexadecimal format')

    if len(tag) > 16:
        raise ValidateError('tag is too long')

    # Passed validation. Pack them.
    data = pack(db, asset, quantity, tag)

    return (source, [], data)
Ejemplo n.º 17
0
 def get_holders(asset):
     asset = util.resolve_subasset_longname(self.db, asset)
     holders = util.holders(self.db, asset, True)
     return holders
Ejemplo n.º 18
0
 def get_holders(asset):
     asset = util.resolve_subasset_longname(db, asset)
     holders = util.holders(db, asset)
     return holders
Ejemplo n.º 19
0
def validate(db, source, asset_dest_quant_list, block_index):
    problems = []

    if len(asset_dest_quant_list) == 0:
        problems.append('send list cannot be empty')

    if len(asset_dest_quant_list) == 1:
        problems.append('send list cannot have only one element')

    if len(asset_dest_quant_list) > 0:
        # Need to manually unpack the tuple to avoid errors on scenarios where no memo is specified
        grpd = groupby([(t[0], t[1]) for t in asset_dest_quant_list])
        lengrps = [len(list(grpr)) for (group, grpr) in grpd]
        cardinality = max(lengrps)
        if cardinality > 1:
            problems.append(
                'cannot specify more than once a destination per asset')

    cursor = db.cursor()
    for t in asset_dest_quant_list:
        # Need to manually unpack the tuple to avoid errors on scenarios where no memo is specified
        asset = util.resolve_subasset_longname(
            db, t[0]) if util.enabled('mpma_validationfix_2323232') else t[0]
        destination = t[1]
        quantity = t[2]

        sendMemo = None
        if len(t) > 3:
            sendMemo = t[3]

        if asset == config.BTC:
            problems.append('cannot send {} to {}'.format(
                config.BTC, destination))

        if not isinstance(quantity, int):
            problems.append(
                'quantities must be an int (in satoshis) for {} to {}'.format(
                    asset, destination))

        if quantity < 0:
            problems.append('negative quantity for {} to {}'.format(
                asset, destination))

        if quantity == 0:
            problems.append('zero quantity for {} to {}'.format(
                asset, destination))

        # For SQLite3
        if quantity > config.MAX_INT:
            problems.append('integer overflow for {} to {}'.format(
                asset, destination))

        # destination is always required
        if not destination:
            problems.append('destination is required for {}'.format(asset))

        if util.enabled('options_require_memo'):
            results = cursor.execute(
                'SELECT options FROM addresses WHERE address=?',
                (destination, ))
            if results:
                result = results.fetchone()
                if result and result[
                        'options'] & config.ADDRESS_OPTION_REQUIRE_MEMO and (
                            sendMemo is None):
                    problems.append(
                        'destination {} requires memo'.format(destination))

        if util.enabled('non_reassignable_assets'
                        ) and asset != config.BTC and asset != config.XCP:
            # verify not senging non-reassignable asset
            issuances = list(
                cursor.execute(
                    '''SELECT * FROM issuances
                                                WHERE asset = ? AND status = ? ORDER BY tx_index DESC LIMIT 1''',
                    (asset, 'valid')))
            if not issuances:
                problems.append('issuance not found (system error?)')
            elif not issuances[0]['reassignable'] and issuances[0][
                    'issuer'] != source and issuances[0][
                        'issuer'] != destination:
                problems.append('{} is a non-reassignable asset'.format(asset))

    return problems
Ejemplo n.º 20
0
def _solve_asset(db, assetName, block_index):
    asset = util.resolve_subasset_longname(db, assetName)
    return util.get_asset_id(db, asset, block_index)
Ejemplo n.º 21
0
def validate(db, source, asset, give_quantity, escrow_quantity, mainchainrate,
             status, open_address, block_index):
    problems = []
    order_match = None
    asset_id = None

    if asset == config.BTC:
        problems.append('cannot dispense %s' % config.BTC)
        return None, problems

    # resolve subassets
    asset = util.resolve_subasset_longname(db, asset)

    if status == STATUS_OPEN or status == STATUS_OPEN_EMPTY_ADDRESS:
        if give_quantity <= 0:
            problems.append('give_quantity must be positive')
        if mainchainrate <= 0:
            problems.append('mainchainrate must be positive')
        if escrow_quantity < give_quantity:
            problems.append(
                'escrow_quantity must be greater or equal than give_quantity')
    elif not (status == STATUS_CLOSED):
        problems.append('invalid status %i' % status)

    cursor = db.cursor()
    cursor.execute(
        '''SELECT quantity FROM balances \
                      WHERE address = ? and asset = ?''', (
            source,
            asset,
        ))
    available = cursor.fetchall()

    if len(available) == 0:
        problems.append('address doesn\'t has the asset %s' % asset)
    elif len(available) >= 1 and available[0]['quantity'] < escrow_quantity:
        problems.append('address doesn\'t has enough balance of %s (%i < %i)' %
                        (asset, available[0]['quantity'], escrow_quantity))
    else:
        if status == STATUS_OPEN_EMPTY_ADDRESS and not (open_address):
            open_address = source
            status = STATUS_OPEN

        query_address = open_address if status == STATUS_OPEN_EMPTY_ADDRESS else source
        cursor.execute(
            '''SELECT * FROM dispensers WHERE source = ? AND asset = ? AND status=?''',
            (query_address, asset, STATUS_OPEN))
        open_dispensers = cursor.fetchall()
        if status == STATUS_OPEN or status == STATUS_OPEN_EMPTY_ADDRESS:
            if len(open_dispensers) > 0 and open_dispensers[0][
                    'satoshirate'] != mainchainrate:
                problems.append(
                    'address has a dispenser already opened for asset %s with a different mainchainrate'
                    % asset)

            if len(open_dispensers) > 0 and open_dispensers[0][
                    'give_quantity'] != give_quantity:
                problems.append(
                    'address has a dispenser already opened for asset %s with a different give_quantity'
                    % asset)
        elif status == STATUS_CLOSED:
            if len(open_dispensers) == 0:
                problems.append(
                    'address doesnt has an open dispenser for asset %s' %
                    asset)

        if status == STATUS_OPEN_EMPTY_ADDRESS:
            cursor.execute(
                '''SELECT count(*) cnt FROM balances WHERE address = ?''',
                (query_address, ))
            existing_balances = cursor.fetchall()
            if existing_balances[0]['cnt'] > 0:
                problems.append(
                    'cannot open on another address if it has any balance history'
                )

        if len(problems) == 0:
            asset_id = util.generate_asset_id(asset, block_index)
            if asset_id == 0:
                problems.append(
                    'cannot dispense %s' %
                    asset)  # How can we test this on a test vector?

    cursor.close()
    if len(problems) > 0:
        return None, problems
    else:
        return asset_id, None