예제 #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)
예제 #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)
예제 #3
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)
예제 #4
0
def compose(db, source, timestamp, value, fee_fraction, text):

    # Store the fee fraction as an integer.
    fee_fraction_int = int(fee_fraction * 1e8)

    problems = validate(db, source, timestamp, value, fee_fraction_int, text,
                        util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    data = struct.pack(config.TXTYPE_FORMAT, ID)

    # always use custom length byte instead of problematic usage of 52p format and make sure to encode('utf-8') for length
    if util.enabled('broadcast_pack_text'):
        data += struct.pack(FORMAT, timestamp, value, fee_fraction_int)
        data += VarIntSerializer.serialize(len(text.encode('utf-8')))
        data += text.encode('utf-8')
    else:
        if len(text) <= 52:
            curr_format = FORMAT + '{}p'.format(len(text) + 1)
        else:
            curr_format = FORMAT + '{}s'.format(len(text))

        data += struct.pack(curr_format, timestamp, value, fee_fraction_int,
                            text.encode('utf-8'))
    return (source, [], data)
예제 #5
0
def compose (db, source, asset, give_quantity, escrow_quantity, mainchainrate, status):
    assetid, problems = validate(db, source, asset, give_quantity, escrow_quantity, mainchainrate, status, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    data = message_type.pack(ID)
    data += struct.pack(FORMAT, assetid, give_quantity, escrow_quantity, mainchainrate, status)
    return (source, [], data)
예제 #6
0
def compose (db, source, feed_address, bet_type, deadline, wager_quantity,
            counterwager_quantity, target_value, leverage, expiration):

    if util.get_balance(db, source, config.XCP) < wager_quantity:
        raise exceptions.ComposeError('insufficient funds')

    problems, leverage = validate(db, source, feed_address, bet_type, deadline, wager_quantity,
                        counterwager_quantity, target_value, leverage, expiration, util.CURRENT_BLOCK_INDEX)
    if util.date_passed(deadline):
        problems.append('deadline passed')
    if problems: raise exceptions.ComposeError(problems)

    data = struct.pack(config.TXTYPE_FORMAT, ID)
    data += struct.pack(FORMAT, bet_type, deadline,
                        wager_quantity, counterwager_quantity, target_value,
                        leverage, expiration)
    return (source, [(feed_address, None)], data)
예제 #7
0
def compose (db, source, asset, give_quantity, escrow_quantity, mainchainrate, status, open_address=None):
    assetid, problems = validate(db, source, asset, give_quantity, escrow_quantity, mainchainrate, status, open_address, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    data = message_type.pack(ID)
    data += struct.pack(FORMAT, assetid, give_quantity, escrow_quantity, mainchainrate, status)
    if status == STATUS_OPEN_EMPTY_ADDRESS and open_address:
        data += address.pack(open_address)
    return (source, [], data)
예제 #8
0
def compose(db, source, possible_moves, wager, move_random_hash, expiration):

    problems = validate(db, source, possible_moves, wager, move_random_hash, expiration, util.CURRENT_BLOCK_INDEX)

    if problems: raise exceptions.ComposeError(problems)

    data = struct.pack(config.TXTYPE_FORMAT, ID)
    data += struct.pack(FORMAT, possible_moves, wager, binascii.unhexlify(move_random_hash), expiration)

    return (source, [], data)
예제 #9
0
def compose(db, source, offer_hash):

    # Check that offer exists.
    offer, offer_type, problems = validate(db, source, offer_hash)
    if problems: raise exceptions.ComposeError(problems)

    offer_hash_bytes = binascii.unhexlify(bytes(offer_hash, 'utf-8'))
    data = struct.pack(config.TXTYPE_FORMAT, ID)
    data += struct.pack(FORMAT, offer_hash_bytes)
    return (source, [], data)
예제 #10
0
def compose(db, source, quantity, overburn=False):
    cursor = db.cursor()
    destination = config.UNSPENDABLE
    problems = validate(db,
                        source,
                        destination,
                        quantity,
                        util.CURRENT_BLOCK_INDEX,
                        overburn=overburn)
    if problems: raise exceptions.ComposeError(problems)

    # Check that a maximum of 1 BTC total is burned per address.
    burns = list(
        cursor.execute(
            '''SELECT * FROM burns WHERE (status = ? AND source = ?)''',
            ('valid', source)))
    already_burned = sum([burn['burned'] for burn in burns])
    if quantity > (1 * config.UNIT - already_burned) and not overburn:
        raise exceptions.ComposeError('1 {} may be burned per address'.format(
            config.BTC))

    cursor.close()
    return (source, [(destination, quantity)], None)
예제 #11
0
def compose (db, source, quantity_per_unit, asset, 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 = struct.pack(config.TXTYPE_FORMAT, ID)
    data += struct.pack(FORMAT_2, quantity_per_unit, asset_id, dividend_asset_id)
    return (source, [], data)
예제 #12
0
def compose (db, source, order_match_id):
    tx0_hash, tx1_hash = util.parse_id(order_match_id)

    destination, btc_quantity, escrowed_asset, escrowed_quantity, order_match, problems = validate(db, source, order_match_id, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    # Warn if down to the wire.
    time_left = order_match['match_expire_index'] - util.CURRENT_BLOCK_INDEX
    if time_left < 4:
        logger.warning('Only {} blocks until that order match expires. The payment might not make into the blockchain in time.'.format(time_left))
    if 10 - time_left < 4:
        logger.warning('Order match has only {} confirmation(s).'.format(10 - time_left))

    tx0_hash_bytes, tx1_hash_bytes = binascii.unhexlify(bytes(tx0_hash, 'utf-8')), binascii.unhexlify(bytes(tx1_hash, 'utf-8'))
    data = struct.pack(config.TXTYPE_FORMAT, ID)
    data += struct.pack(FORMAT, tx0_hash_bytes, tx1_hash_bytes)
    return (source, [(destination, btc_quantity)], data)
예제 #13
0
def compose(db, source, timestamp, value, fee_fraction, text):

    # Store the fee fraction as an integer.
    fee_fraction_int = int(fee_fraction * 1e8)

    problems = validate(db, source, timestamp, value, fee_fraction_int, text,
                        util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    data = struct.pack(config.TXTYPE_FORMAT, ID)
    if len(text) <= 52:
        curr_format = FORMAT + '{}p'.format(len(text) + 1)
    else:
        curr_format = FORMAT + '{}s'.format(len(text))
    data += struct.pack(curr_format, timestamp, value, fee_fraction_int,
                        text.encode('utf-8'))
    return (source, [], data)
예제 #14
0
def compose (db, source, move, random, rps_match_id):
    tx0_hash, tx1_hash = util.parse_id(rps_match_id)

    txn, rps_match, problems = validate(db, source, move, random, rps_match_id)
    if problems: raise exceptions.ComposeError(problems)

    # Warn if down to the wire.
    time_left = rps_match['match_expire_index'] - util.CURRENT_BLOCK_INDEX
    if time_left < 4:
        logger.warning('Only {} blocks until that rps match expires. The conclusion might not make into the blockchain in time.'.format(time_left))

    tx0_hash_bytes = binascii.unhexlify(bytes(tx0_hash, 'utf-8'))
    tx1_hash_bytes = binascii.unhexlify(bytes(tx1_hash, 'utf-8'))
    random_bytes = binascii.unhexlify(bytes(random, 'utf-8'))
    data = struct.pack(config.TXTYPE_FORMAT, ID)
    data += struct.pack(FORMAT, move, random_bytes, tx0_hash_bytes, tx1_hash_bytes)
    return (source, [], data)
예제 #15
0
def compose(db,
            source,
            destination,
            asset,
            quantity,
            memo=None,
            memo_is_hex=False,
            use_enhanced_send=None):
    # special case - enhanced_send replaces send by default when it is enabled
    #   but it can be explicitly disabled with an API parameter
    if util.enabled('enhanced_sends'):
        if use_enhanced_send is None or use_enhanced_send == True:
            return enhanced_send.compose(db, source, destination, asset,
                                         quantity, memo, memo_is_hex)
    elif memo is not None or use_enhanced_send == True:
        raise exceptions.ComposeError('enhanced sends are not enabled')

    return send1.compose(db, source, destination, asset, quantity)
예제 #16
0
def compose(db, source, transfer_destination, asset, quantity, divisible,
            description):

    # Callability is depreciated, so for re‐issuances set relevant parameters
    # to old values; for first issuances, make uncallable.
    cursor = db.cursor()
    cursor.execute(
        '''SELECT * FROM issuances \
                      WHERE (status = ? AND asset = ?)
                      ORDER BY tx_index ASC''', ('valid', asset))
    issuances = cursor.fetchall()
    if issuances:
        last_issuance = issuances[-1]
        callable_ = last_issuance['callable']
        call_date = last_issuance['call_date']
        call_price = last_issuance['call_price']
    else:
        callable_ = False
        call_date = 0
        call_price = 0.0
    cursor.close()

    call_date, call_price, problems, fee, description, divisible, reissuance = validate(
        db, source, transfer_destination, asset, quantity, divisible,
        callable_, call_date, call_price, description,
        util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    asset_id = util.generate_asset_id(asset, util.CURRENT_BLOCK_INDEX)
    data = struct.pack(config.TXTYPE_FORMAT, ID)
    if len(description) <= 42:
        curr_format = FORMAT_2 + '{}p'.format(len(description) + 1)
    else:
        curr_format = FORMAT_2 + '{}s'.format(len(description))
    data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0,
                        1 if callable_ else 0, call_date or 0, call_price
                        or 0.0, description.encode('utf-8'))
    if transfer_destination:
        destination_outputs = [(transfer_destination, None)]
    else:
        destination_outputs = []
    return (source, destination_outputs, data)
예제 #17
0
def compose (db, source, destination, flags, memo):
    if memo is None:
        memo = b''
    elif flags & FLAG_BINARY_MEMO:
        memo = bytes.fromhex(memo)
    else:
        memo = memo.encode('utf-8')
        memo = struct.pack(">{}s".format(len(memo)), memo)

    block_index = util.CURRENT_BLOCK_INDEX
    problems = validate(db, source, destination, flags, memo, block_index)
    if problems: raise exceptions.ComposeError(problems)

    short_address_bytes = address.pack(destination)

    data = message_type.pack(ID)
    data += struct.pack(FORMAT, short_address_bytes, flags)
    data += memo

    return (source, [], data)
예제 #18
0
def compose(db, source, transfer_destination, asset, quantity, divisible,
            description):

    # Callability is deprecated, so for re‐issuances set relevant parameters
    # to old values; for first issuances, make uncallable.
    cursor = db.cursor()
    cursor.execute(
        '''SELECT * FROM issuances \
                      WHERE (status = ? AND asset = ?)
                      ORDER BY tx_index ASC''', ('valid', asset))
    issuances = cursor.fetchall()
    if issuances:
        last_issuance = issuances[-1]
        callable_ = last_issuance['callable']
        call_date = last_issuance['call_date']
        call_price = last_issuance['call_price']
    else:
        callable_ = False
        call_date = 0
        call_price = 0.0
    cursor.close()

    # check subasset
    subasset_parent = None
    subasset_longname = None
    if util.enabled('subassets'):  # Protocol change.
        subasset_parent, subasset_longname = util.parse_subasset_from_asset_name(
            asset)
        if subasset_longname is not None:
            # try to find an existing subasset
            sa_cursor = db.cursor()
            sa_cursor.execute(
                '''SELECT * FROM assets \
                              WHERE (asset_longname = ?)''',
                (subasset_longname, ))
            assets = sa_cursor.fetchall()
            sa_cursor.close()
            if len(assets) > 0:
                # this is a reissuance
                asset = assets[0]['asset_name']
            else:
                # this is a new issuance
                #   generate a random numeric asset id which will map to this subasset
                asset = util.generate_random_asset()

    call_date, call_price, problems, fee, description, divisible, reissuance, reissued_asset_longname = validate(
        db, source, transfer_destination, asset, quantity, divisible,
        callable_, call_date, call_price, description, subasset_parent,
        subasset_longname, util.CURRENT_BLOCK_INDEX)
    if problems: raise exceptions.ComposeError(problems)

    asset_id = util.generate_asset_id(asset, util.CURRENT_BLOCK_INDEX)
    if subasset_longname is None or reissuance:
        # Type 20 standard issuance FORMAT_2 >QQ??If
        #   used for standard issuances and all reissuances
        data = message_type.pack(ID)
        if len(description) <= 42:
            curr_format = FORMAT_2 + '{}p'.format(len(description) + 1)
        else:
            curr_format = FORMAT_2 + '{}s'.format(len(description))
        data += struct.pack(curr_format, asset_id, quantity,
                            1 if divisible else 0, 1 if callable_ else 0,
                            call_date or 0, call_price or 0.0,
                            description.encode('utf-8'))
    else:
        # Type 21 subasset issuance SUBASSET_FORMAT >QQ?B
        #   Used only for initial subasset issuance
        # compacts a subasset name to save space
        compacted_subasset_longname = util.compact_subasset_longname(
            subasset_longname)
        compacted_subasset_length = len(compacted_subasset_longname)
        data = message_type.pack(SUBASSET_ID)
        curr_format = SUBASSET_FORMAT + '{}s'.format(
            compacted_subasset_length) + '{}s'.format(len(description))
        data += struct.pack(curr_format, asset_id, quantity,
                            1 if divisible else 0, compacted_subasset_length,
                            compacted_subasset_longname,
                            description.encode('utf-8'))

    if transfer_destination:
        destination_outputs = [(transfer_destination, None)]
    else:
        destination_outputs = []
    return (source, destination_outputs, data)
예제 #19
0
def compose(db,
            source,
            destination,
            asset,
            quantity,
            memo=None,
            memo_is_hex=False,
            use_enhanced_send=None):
    # special case - enhanced_send replaces send by default when it is enabled
    #   but it can be explicitly disabled with an API parameter
    if util.enabled('enhanced_sends'):
        # Another special case, if destination, asset and quantity are arrays, it's an MPMA send
        if isinstance(destination, list) and isinstance(
                asset, list) and isinstance(quantity, list):
            if util.enabled('mpma_sends'):
                if len(destination) == len(asset) and len(asset) == len(
                        quantity):
                    # Sending memos in a MPMA message can be done by several approaches:
                    # 1. Send a list of memos, there must be one for each send and they correspond to the sends by index
                    #   - In this case memo_is_hex should be a list with the same cardinality
                    # 2. Send a dict with the message specific memos and the message wide memo (same for the hex specifier):
                    #   - Each dict should have 2 members:
                    #     + list: same as case (1). An array that specifies the memo for each send
                    #     + msg_wide: the memo for the whole message. This memo will be used for sends that don't have a memo specified. Same as in (3)
                    # 3. Send one memo (either bytes or string) and True/False in memo_is_hex. This will be interpreted as a message wide memo.
                    if (len(destination) > config.MPMA_LIMIT):
                        raise exceptions.ComposeError(
                            'mpma sends have a maximum of ' +
                            str(config.MPMA_LIMIT) + ' sends')

                    if isinstance(memo, list) and isinstance(
                            memo_is_hex, list):
                        # (1) implemented here
                        if len(memo) != len(memo_is_hex):
                            raise exceptions.ComposeError(
                                'memo and memo_is_hex lists should have the same length'
                            )
                        elif len(memo) != len(destination):
                            raise exceptions.ComposeError(
                                'memo/memo_is_hex lists should have the same length as sends'
                            )

                        return mpma.compose(
                            db, source,
                            util.flat(
                                zip(asset, destination, quantity, memo,
                                    memo_is_hex)), None, None)
                    elif isinstance(memo, dict) and isinstance(
                            memo_is_hex, dict):
                        # (2) implemented here
                        if not ('list' in memo and 'list' in memo_is_hex
                                and 'msg_wide' in memo
                                and 'msg_wide' in memo_is_hex):
                            raise exceptions.ComposeError(
                                'when specifying memo/memo_is_hex as a dict, they must contain keys "list" and "msg_wide"'
                            )
                        elif len(memo['list']) != len(memo_is_hex['list']):
                            raise exceptions.ComposeError(
                                'length of memo.list and memo_is_hex.list must be equal'
                            )
                        elif len(memo['list']) != len(destination):
                            raise exceptions.ComposeError(
                                'length of memo.list/memo_is_hex.list must be equal to the amount of sends'
                            )

                        return mpma.compose(
                            db, source,
                            util.flat(
                                zip(asset, destination, quantity, memo['list'],
                                    memo_is_hex['list'])), memo['msg_wide'],
                            memo_is_hex['msg_wide'])
                    else:
                        # (3) the default case
                        return mpma.compose(
                            db, source,
                            util.flat(zip(asset, destination, quantity)), memo,
                            memo_is_hex)
                else:
                    raise exceptions.ComposeError(
                        'destination, asset and quantity arrays must have the same amount of elements'
                    )
            else:
                raise exceptions.ComposeError('mpma sends are not enabled')
        elif use_enhanced_send is None or use_enhanced_send == True:
            return enhanced_send.compose(db, source, destination, asset,
                                         quantity, memo, memo_is_hex)
    elif memo is not None or use_enhanced_send == True:
        raise exceptions.ComposeError('enhanced sends are not enabled')

    return send1.compose(db, source, destination, asset, quantity)