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 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
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))}
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)
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)
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)
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)
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)
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)
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
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
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, 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)
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)
def get_holders(asset): asset = util.resolve_subasset_longname(self.db, asset) holders = util.holders(self.db, asset, True) return holders
def get_holders(asset): asset = util.resolve_subasset_longname(db, asset) holders = util.holders(db, asset) return holders
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
def _solve_asset(db, assetName, block_index): asset = util.resolve_subasset_longname(db, assetName) return util.get_asset_id(db, asset, block_index)
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