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)
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)
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, 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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)