def __init__(self): client = mqtt.Client() client.on_connect = self.on_connect client.on_message = self.on_message client.username_pw_set(TOKEN) gpio = GPIOStatus(17, client) dispatcher.add_method(gpio.get_status) dispatcher.add_method(gpio.set_status) dispatcher.add_method(gpio.toggle) self.client = client self.gpio = gpio client.connect(IOTA_HOST, 1883, 60)
def add_method(self, method): dispatcher.add_method(method)
from jsonrpc import dispatcher def dict_to_list(dictionary): return list(dictionary.items()) @dispatcher.add_method def simple_add(first=0, **kwargs): return first + kwargs["second"] def echo_with_long_name(msg): return msg dispatcher.add_method(echo_with_long_name, name='echo') dispatcher['subtract'] = lambda a, b: a - b dispatcher['dict_to_list'] = dict_to_list
def run(self): logger.info('Starting API Server.') self.db = self.db or database.get_connection(read_only=True, integrity_check=False) app = flask.Flask(__name__) auth = HTTPBasicAuth() @auth.get_password def get_pw(username): if username == config.RPC_USER: return config.RPC_PASSWORD return None ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): try: return get_rows(self.db, table=table, **kwargs) except TypeError as e: #TODO: generalise for all API methods raise APIError(str(e)) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=None): if bindings == None: bindings = [] return db_query(self.db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} methods def generate_create_method(tx): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_args, private_key_wif def create_method(**kwargs): try: transaction_args, common_args, private_key_wif = split_params( **kwargs) return compose_transaction(self.db, name=tx, params=transaction_args, **common_args) except (TypeError, script.AddressError, exceptions.ComposeError, exceptions.TransactionError, exceptions.BalanceError) as error: # TypeError happens when unexpected keyword arguments are passed in error_msg = "Error composing {} transaction via API: {}".format( tx, str(error)) logging.warning(error_msg) logging.warning(traceback.format_exc()) raise JSONRPCDispatchException( code=JSON_RPC_ERROR_API_COMPOSE, message=error_msg) return create_method for tx in API_TRANSACTIONS: create_method = generate_create_method(tx) create_method.__name__ = 'create_{}'.format(tx) dispatcher.add_method(create_method) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise APIError("block_index must be an integer.") cursor = self.db.cursor() cursor.execute( 'select * from messages where block_index = ? order by message_index asc', (block_index, )) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [ message_indexes, ] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise APIError( "All items in message_indexes are not integers") cursor = self.db.cursor() cursor.execute( 'SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]), )) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method 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) @dispatcher.add_method def get_xcp_supply(): logger.warning("Deprecated method: `get_xcp_supply`") return util.xcp_supply(self.db) @dispatcher.add_method 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, '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))) cursor.close() 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']), 'locked': locked, 'supply': util.asset_supply(self.db, asset), 'description': last_issuance['description'], 'issuer': last_issuance['issuer'] }) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = self.db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index, )) blocks = list(cursor) if len(blocks) == 1: block = blocks[0] elif len(blocks) == 0: raise exceptions.DatabaseError('No blocks found.') else: assert False cursor.close() return block @dispatcher.add_method def fee_per_kb(conf_target=config.ESTIMATE_FEE_CONF_TARGET, mode=config.ESTIMATE_FEE_MODE): return backend.fee_per_kb(conf_target, mode) @dispatcher.add_method def get_blocks(block_indexes, min_message_index=None): """fetches block info and messages for the specified block indexes @param min_message_index: Retrieve blocks from the message feed on or after this specific message index (useful since blocks may appear in the message feed more than once, if a reorg occurred). Note that if this parameter is not specified, the messages for the first block will be returned. """ if not isinstance(block_indexes, (list, tuple)): raise APIError("block_indexes must be a list of integers.") if len(block_indexes) >= 250: raise APIError("can only specify up to 250 indexes at a time.") block_indexes_str = ','.join([str(x) for x in block_indexes]) cursor = self.db.cursor() # The blocks table gets rolled back from undolog, so min_message_index doesn't matter for this query cursor.execute( 'SELECT * FROM blocks WHERE block_index IN (%s) ORDER BY block_index ASC' % (block_indexes_str, )) blocks = cursor.fetchall() cursor.execute( 'SELECT * FROM messages WHERE block_index IN (%s) ORDER BY message_index ASC' % (block_indexes_str, )) messages = collections.deque(cursor.fetchall()) # Discard any messages less than min_message_index if min_message_index: while len(messages) and messages[0][ 'message_index'] < min_message_index: messages.popleft() # Packages messages into their appropriate block in the data structure to be returned for block in blocks: block['_messages'] = [] while len(messages) and messages[0]['block_index'] == block[ 'block_index']: block['_messages'].append(messages.popleft()) #NOTE: if len(messages), then we're only returning the messages for the first set of blocks before the reorg cursor.close() return blocks @dispatcher.add_method def get_running_info(): latestBlockIndex = backend.getblockcount() try: check_database_state(self.db, latestBlockIndex) except DatabaseError: caught_up = False else: caught_up = True try: cursor = self.db.cursor() blocks = list( cursor.execute( '''SELECT * FROM blocks WHERE block_index = ?''', (util.CURRENT_BLOCK_INDEX, ))) assert len(blocks) == 1 last_block = blocks[0] cursor.close() except: last_block = None try: last_message = util.last_message(self.db) except: last_message = None try: indexd_blocks_behind = backend.getindexblocksbehind() except: indexd_blocks_behind = latestBlockIndex if latestBlockIndex > 0 else 999999 indexd_caught_up = indexd_blocks_behind <= 1 server_ready = caught_up and indexd_caught_up return { 'server_ready': server_ready, 'db_caught_up': caught_up, 'bitcoin_block_count': latestBlockIndex, 'last_block': last_block, 'indexd_caught_up': indexd_caught_up, 'indexd_blocks_behind': indexd_blocks_behind, 'last_message_index': last_message['message_index'] if last_message else -1, 'api_limit_rows': config.API_LIMIT_ROWS, 'running_testnet': config.TESTNET, 'running_regtest': config.REGTEST, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def get_element_counts(): counts = {} cursor = self.db.cursor() for element in [ 'transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'btcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages', 'destructions' ]: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(longnames=False): cursor = self.db.cursor() if longnames: names = [] for row in cursor.execute( "SELECT asset, asset_longname FROM issuances WHERE status = 'valid' GROUP BY asset ORDER BY asset ASC" ): names.append({ 'asset': row['asset'], 'asset_longname': row['asset_longname'] }) else: names = [ row['asset'] for row in cursor.execute( "SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC" ) ] cursor.close() return names @dispatcher.add_method def get_asset_longnames(): return get_asset_names(longnames=True) @dispatcher.add_method 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))} @dispatcher.add_method def get_holders(asset): asset = util.resolve_subasset_longname(self.db, asset) holders = util.holders(self.db, asset, True) return holders @dispatcher.add_method def search_raw_transactions(address, unconfirmed=True): return backend.search_raw_transactions(address, unconfirmed=unconfirmed) @dispatcher.add_method def get_unspent_txouts(address, unconfirmed=False, unspent_tx_hash=None): return backend.get_unspent_txouts(address, unconfirmed=unconfirmed, unspent_tx_hash=unspent_tx_hash) @dispatcher.add_method def getrawtransaction(tx_hash, verbose=False, skip_missing=False): return backend.getrawtransaction(tx_hash, verbose=verbose, skip_missing=skip_missing) @dispatcher.add_method def getrawtransaction_batch(txhash_list, verbose=False, skip_missing=False): return backend.getrawtransaction_batch(txhash_list, verbose=verbose, skip_missing=skip_missing) @dispatcher.add_method def get_tx_info(tx_hex, block_index=None): # block_index mandatory for transactions before block 335000 source, destination, btc_amount, fee, data, extra = blocks.get_tx_info( tx_hex, block_index=block_index) return source, destination, btc_amount, fee, util.hexlify( data) if data else '' @dispatcher.add_method def unpack(data_hex): data = binascii.unhexlify(data_hex) message_type_id, message = message_type.unpack(data) # TODO: Enabled only for `send`. if message_type_id == send.ID: unpack_method = send.unpack elif message_type_id == enhanced_send.ID: unpack_method = enhanced_send.unpack else: raise APIError('unsupported message type') unpacked = unpack_method(self.db, message, util.CURRENT_BLOCK_INDEX) return message_type_id, unpacked @dispatcher.add_method # TODO: Rename this method. def search_pubkey(pubkeyhash, provided_pubkeys=None): return backend.pubkeyhash_to_pubkey( pubkeyhash, provided_pubkeys=provided_pubkeys) def _set_cors_headers(response): if not config.RPC_NO_ALLOW_CORS: response.headers['Access-Control-Allow-Origin'] = '*' response.headers[ 'Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response.headers[ 'Access-Control-Allow-Headers'] = 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' @app.route('/healthz', methods=['GET']) def handle_healthz(): msg, code = 'Healthy', 200 try: latestBlockIndex = backend.getblockcount() check_database_state(self.db, latestBlockIndex) except DatabaseError: msg, code = 'Unhealthy', 503 return flask.Response(msg, code, mimetype='text/plain') @app.route('/', defaults={'args_path': ''}, methods=['GET', 'POST', 'OPTIONS']) @app.route('/<path:args_path>', methods=['GET', 'POST', 'OPTIONS']) # Only require authentication if RPC_PASSWORD is set. @conditional_decorator(auth.login_required, hasattr(config, 'RPC_PASSWORD')) def handle_root(args_path): """Handle all paths, decide where to forward the query.""" if args_path == '' or args_path.startswith('api/') or args_path.startswith('API/') or \ args_path.startswith('rpc/') or args_path.startswith('RPC/'): if flask.request.method == 'POST': # Need to get those here because it might not be available in this aux function. request_json = flask.request.get_data().decode('utf-8') response = handle_rpc_post(request_json) return response elif flask.request.method == 'OPTIONS': response = handle_rpc_options() return response else: error = 'Invalid method.' return flask.Response(error, 405, mimetype='application/json') elif args_path.startswith('rest/') or args_path.startswith( 'REST/'): if flask.request.method == 'GET' or flask.request.method == 'POST': # Pass the URL path without /REST/ part and Flask request object. rest_path = args_path.split('/', 1)[1] response = handle_rest(rest_path, flask.request) return response else: error = 'Invalid method.' return flask.Response(error, 405, mimetype='application/json') else: # Not found return flask.Response(None, 404, mimetype='application/json') ###################### # JSON-RPC API ###################### def handle_rpc_options(): response = flask.Response('', 204) _set_cors_headers(response) return response def handle_rpc_post(request_json): """Handle /API/ POST route. Call relevant get_rows/create_transaction wrapper.""" # Check for valid request format. try: request_data = json.loads(request_json) assert 'id' in request_data and request_data[ 'jsonrpc'] == "2.0" and request_data['method'] # params may be omitted except: obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data="Invalid JSON-RPC 2.0 request format") return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Only arguments passed as a `dict` are supported. if request_data.get('params', None) and not isinstance( request_data['params'], dict): obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data= 'Arguments must be passed as a JSON object (list of unnamed arguments not supported)' ) return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Return an error if the API Status Poller checks fail. if not config.FORCE and current_api_status_code: return flask.Response(current_api_status_response_json, 503, mimetype='application/json') # Answer request normally. # NOTE: `UnboundLocalError: local variable 'output' referenced before assignment` means the method doesn’t return anything. jsonrpc_response = jsonrpc.JSONRPCResponseManager.handle( request_json, dispatcher) response = flask.Response(jsonrpc_response.json.encode(), 200, mimetype='application/json') _set_cors_headers(response) return response ###################### # HTTP REST API ###################### def handle_rest(path_args, flask_request): """Handle /REST/ route. Query the database using get_rows or create transaction using compose_transaction.""" url_action = flask_request.path.split('/')[-1] if url_action == 'compose': compose = True elif url_action == 'get': compose = False else: error = 'Invalid action "%s".' % url_action return flask.Response(error, 400, mimetype='application/json') # Get all arguments passed via URL. url_args = path_args.split('/') try: query_type = url_args.pop(0).lower() except IndexError: error = 'No query_type provided.' return flask.Response(error, 400, mimetype='application/json') # Check if message type or table name are valid. if (compose and query_type not in API_TRANSACTIONS) or \ (not compose and query_type not in API_TABLES): error = 'No such query type in supported queries: "%s".' % query_type return flask.Response(error, 400, mimetype='application/json') # Parse the additional arguments. extra_args = flask_request.args.items() query_data = {} if compose: common_args = {} transaction_args = {} for (key, value) in extra_args: # Determine value type. try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass # Split keys into common and transaction-specific arguments. Discard the privkey. if key in COMMONS_ARGS: common_args[key] = value elif key == 'privkey': pass else: transaction_args[key] = value # Must have some additional transaction arguments. if not len(transaction_args): error = 'No transaction arguments provided.' return flask.Response(error, 400, mimetype='application/json') # Compose the transaction. try: query_data = compose_transaction(self.db, name=query_type, params=transaction_args, **common_args) except (script.AddressError, exceptions.ComposeError, exceptions.TransactionError, exceptions.BalanceError) as error: error_msg = logging.warning( "{} -- error composing {} transaction via API: {}". format(str(error.__class__.__name__), query_type, str(error))) return flask.Response(error_msg, 400, mimetype='application/json') else: # Need to de-generate extra_args to pass it through. query_args = dict([item for item in extra_args]) operator = query_args.pop('op', 'AND') # Put the data into specific dictionary format. data_filter = [{ 'field': key, 'op': '==', 'value': value } for (key, value) in query_args.items()] # Run the query. try: query_data = get_rows(self.db, table=query_type, filters=data_filter, filterop=operator) except APIError as error: return flask.Response(str(error), 400, mimetype='application/json') # See which encoding to choose from. file_format = flask_request.headers['Accept'] # JSON as default. if file_format == 'application/json' or file_format == '*/*': response_data = json.dumps(query_data) elif file_format == 'application/xml': # Add document root for XML. Note when xmltodict encounters a list, it produces separate tags for every item. # Hence we end up with multiple query_type roots. To combat this we put it in a separate item dict. response_data = serialize_to_xml( {query_type: { 'item': query_data }}) else: error = 'Invalid file format: "%s".' % file_format return flask.Response(error, 400, mimetype='application/json') response = flask.Response(response_data, 200, mimetype=file_format) return response # Init the HTTP Server. init_api_access_log(app) # Run app server (blocking) self.is_ready = True app.run(host=config.RPC_HOST, port=config.RPC_PORT, threaded=True) self.db.close() return
def run (self): db = util.connect_to_db(flags='SQLITE_OPEN_READONLY') ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): return get_rows(db, table=table, **kwargs) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=[]): return db_query(db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} and do_{transaction} methods def generate_create_method(transaction): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_args, private_key_wif def create_method(**kwargs): transaction_args, common_args, private_key_wif = split_params(**kwargs) return compose_transaction(db, name=transaction, params=transaction_args, **common_args) def do_method(**kwargs): transaction_args, common_args, private_key_wif = split_params(**kwargs) return do_transaction(db, name=transaction, params=transaction_args, private_key_wif=private_key_wif, **common_args) return create_method, do_method for transaction in API_TRANSACTIONS: create_method, do_method = generate_create_method(transaction) create_method.__name__ = 'create_{}'.format(transaction) do_method.__name__ = 'do_{}'.format(transaction) dispatcher.add_method(create_method) dispatcher.add_method(do_method) @dispatcher.add_method def sign_tx(unsigned_tx_hex, privkey=None): return sign_transaction(unsigned_tx_hex, private_key_wif=privkey) @dispatcher.add_method def broadcast_tx(signed_tx_hex): return broadcast_transaction(signed_tx_hex) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise Exception("block_index must be an integer.") cursor = db.cursor() cursor.execute('select * from messages where block_index = ? order by message_index asc', (block_index,)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [message_indexes,] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise Exception("All items in message_indexes are not integers") cursor = db.cursor() cursor.execute('SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]),)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_xcp_supply(): return util.xcp_supply(db) @dispatcher.add_method def get_asset_info(assets): if not isinstance(assets, list): raise Exception("assets must be a list of asset names, even if it just contains one entry") assetsInfo = [] for asset in assets: # BTC and XCP. if asset in [config.BTC, config.XCP]: if asset == config.BTC: supply = bitcoin.get_btc_supply(normalize=False) else: supply = util.xcp_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'callable': False, 'call_date': None, 'call_price': None, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: break #asset not found, most likely else: last_issuance = issuances[-1] supply = 0 locked = False for e in issuances: if e['locked']: locked = True supply += e['quantity'] assetsInfo.append({ 'asset': asset, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': supply, 'callable': bool(last_issuance['callable']), 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'issuer': last_issuance['issuer']}) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)) try: blocks = list(cursor) assert len(blocks) == 1 block = blocks[0] except IndexError: raise exceptions.DatabaseError('No blocks found.') cursor.close() return block @dispatcher.add_method def get_running_info(): latestBlockIndex = bitcoin.get_block_count() try: util.database_check(db, latestBlockIndex) except: caught_up = False else: caught_up = True try: last_block = util.last_block(db) except: last_block = {'block_index': None, 'block_hash': None, 'block_time': None} try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'bitcoin_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in ['transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'btcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'callbacks', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages']: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names class API(object): @cherrypy.expose def index(self): try: data = cherrypy.request.body.read().decode('utf-8') except ValueError: raise cherrypy.HTTPError(400, 'Invalid JSON document') cherrypy.response.headers["Content-Type"] = "application/json" #CORS logic is handled in the nginx config # Check version. # Check that bitcoind is running, communicable, and caught up with the blockchain. # Check that the database has caught up with bitcoind. if not config.FORCE: try: self.last_check except: self.last_check = 0 try: if time.time() - self.last_check >= 4 * 3600: # Four hours since last check. code = 10 util.version_check(db) if time.time() - self.last_check > 10 * 60: # Ten minutes since last check. code = 11 bitcoin.bitcoind_check(db) code = 12 util.database_check(db, bitcoin.get_block_count()) # TODO: If not reparse or rollback, once those use API. self.last_check = time.time() except Exception as e: exception_name = e.__class__.__name__ exception_text = str(e) response = jsonrpc.exceptions.JSONRPCError(code=code, message=exception_name, data=exception_text) return response.json.encode() response = jsonrpc.JSONRPCResponseManager.handle(data, dispatcher) return response.json.encode() cherrypy.config.update({ 'log.screen': False, "environment": "embedded", 'log.error_log.propagate': False, 'log.access_log.propagate': False, "server.logToScreen" : False }) checkpassword = cherrypy.lib.auth_basic.checkpassword_dict( {config.RPC_USER: config.RPC_PASSWORD}) app_config = { '/': { 'tools.trailing_slash.on': False, 'tools.auth_basic.on': True, 'tools.auth_basic.realm': config.XCP_CLIENT, 'tools.auth_basic.checkpassword': checkpassword, }, } application = cherrypy.Application(API(), script_name="/api", config=app_config) #disable logging of the access and error logs to the screen application.log.access_log.propagate = False application.log.error_log.propagate = False if not config.UNITTEST: #skip setting up logs when for the test suite #set up a rotating log handler for this application # Remove the default FileHandlers if present. application.log.error_file = "" application.log.access_file = "" maxBytes = getattr(application.log, "rot_maxBytes", 10000000) backupCount = getattr(application.log, "rot_backupCount", 1000) # Make a new RotatingFileHandler for the error log. fname = getattr(application.log, "rot_error_file", os.path.join(config.DATA_DIR, "api.error.log")) h = logging_handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) h.setLevel(logging.DEBUG) h.setFormatter(cherrypy._cplogging.logfmt) application.log.error_log.addHandler(h) # Make a new RotatingFileHandler for the access log. fname = getattr(application.log, "rot_access_file", os.path.join(config.DATA_DIR, "api.access.log")) h = logging_handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) h.setLevel(logging.DEBUG) h.setFormatter(cherrypy._cplogging.logfmt) application.log.access_log.addHandler(h) #start up the API listener/handler server = wsgiserver.CherryPyWSGIServer((config.RPC_HOST, config.RPC_PORT), application, numthreads=config.API_NUM_THREADS, request_queue_size=config.API_REQUEST_QUEUE_SIZE) #logging.debug("Initializing API interface…") try: server.start() except OSError: raise Exception("Cannot start the API subsystem. Is {} already running, or is something else listening on port {}?".format(config.XCP_CLIENT, config.RPC_PORT))
def run(self): logger.info('Starting API Server.') db = database.get_connection(read_only=True, integrity_check=False) app = flask.Flask(__name__) auth = HTTPBasicAuth() @auth.get_password def get_pw(username): if username == config.RPC_USER: return config.RPC_PASSWORD return None ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): try: return get_rows(db, table=table, **kwargs) except TypeError as e: #TODO: generalise for all API methods raise APIError(str(e)) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=None): if bindings == None: bindings = [] return db_query(db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} methods def generate_create_method(tx): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_args, private_key_wif def create_method(**kwargs): try: transaction_args, common_args, private_key_wif = split_params(**kwargs) return compose_transaction(db, name=tx, params=transaction_args, **common_args) except TypeError as e: #TODO: generalise for all API methods raise APIError(str(e)) return create_method for tx in API_TRANSACTIONS: create_method = generate_create_method(tx) create_method.__name__ = 'create_{}'.format(tx) dispatcher.add_method(create_method) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise APIError("block_index must be an integer.") cursor = db.cursor() cursor.execute('select * from messages where block_index = ? order by message_index asc', (block_index,)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [message_indexes,] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise APIError("All items in message_indexes are not integers") cursor = db.cursor() cursor.execute('SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]),)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_supply(asset): if asset == 'SCH': return backend.get_shell_supply(normalize=False) elif asset == 'SHP': return util.shp_supply(db) else: return util.asset_supply(db, asset) @dispatcher.add_method def get_shp_supply(): logger.warning("Deprecated method: `get_shp_supply`") return util.shp_supply(db) @dispatcher.add_method 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: # SCH and SHP. if asset in [config.SCH, config.SHP]: if asset == config.SCH: supply = backend.get_shell_supply(normalize=False) else: supply = util.shp_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() 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, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': util.asset_supply(db, asset), 'description': last_issuance['description'], 'issuer': last_issuance['issuer']}) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)) blocks = list(cursor) if len(blocks) == 1: block = blocks[0] elif len(blocks) == 0: raise exceptions.DatabaseError('No blocks found.') else: assert False cursor.close() return block @dispatcher.add_method def get_blocks(block_indexes): """fetches block info and messages for the specified block indexes""" if not isinstance(block_indexes, (list, tuple)): raise APIError("block_indexes must be a list of integers.") if len(block_indexes) >= 250: raise APIError("can only specify up to 250 indexes at a time.") block_indexes_str = ','.join([str(x) for x in block_indexes]) cursor = db.cursor() cursor.execute('SELECT * FROM blocks WHERE block_index IN (%s) ORDER BY block_index ASC' % (block_indexes_str,)) blocks = cursor.fetchall() cursor.execute('SELECT * FROM messages WHERE block_index IN (%s) ORDER BY block_index ASC, message_index ASC' % (block_indexes_str,)) messages = collections.deque(cursor.fetchall()) for block in blocks: # messages_in_block = [] block['_messages'] = [] while len(messages) and messages[0]['block_index'] == block['block_index']: block['_messages'].append(messages.popleft()) assert not len(messages) #should have been cleared out cursor.close() return blocks @dispatcher.add_method def get_running_info(): latestBlockIndex = backend.getblockcount() try: check_database_state(db, latestBlockIndex) except DatabaseError: caught_up = False else: caught_up = True try: cursor = db.cursor() blocks = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (util.CURRENT_BLOCK_INDEX, ))) assert len(blocks) == 1 last_block = blocks[0] cursor.close() except: last_block = None try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'SatoshiChain_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in ['transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'shellpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages']: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names @dispatcher.add_method def get_holder_count(asset): holders = util.holders(db, asset) addresses = [] for holder in holders: addresses.append(holder['address']) return {asset: len(set(addresses))} @dispatcher.add_method def get_holders(asset): holders = util.holders(db, asset) return holders @dispatcher.add_method def search_raw_transactions(address, unconfirmed=True): return backend.searchrawtransactions(address, unconfirmed=unconfirmed) @dispatcher.add_method def get_unspent_txouts(address, unconfirmed=False): return backend.get_unspent_txouts(address, unconfirmed=unconfirmed, multisig_inputs=False) @dispatcher.add_method def get_tx_info(tx_hex, block_index=None): # block_index mandatory for transactions before block 335000 source, destination, shell_amount, fee, data = blocks.get_tx_info(tx_hex, block_index=block_index) return source, destination, shell_amount, fee, util.hexlify(data) if data else '' @dispatcher.add_method def unpack(data_hex): data = binascii.unhexlify(data_hex) message_type_id = struct.unpack(config.TXTYPE_FORMAT, data[:4])[0] message = data[4:] # TODO: Enabled only for `send`. if message_type_id == send.ID: unpack_method = send.unpack else: raise APIError('unsupported message type') unpacked = unpack_method(db, message, util.CURRENT_BLOCK_INDEX) return message_type_id, unpacked @dispatcher.add_method # TODO: Rename this method. def search_pubkey(pubkeyhash, provided_pubkeys=None): return backend.pubkeyhash_to_pubkey(pubkeyhash, provided_pubkeys=provided_pubkeys) def _set_cors_headers(response): if not config.RPC_NO_ALLOW_CORS: response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' @app.route('/', defaults={'args_path': ''}, methods=['GET', 'POST', 'OPTIONS']) @app.route('/<path:args_path>', methods=['GET', 'POST', 'OPTIONS']) # Only require authentication if RPC_PASSWORD is set. @conditional_decorator(auth.login_required, hasattr(config, 'RPC_PASSWORD')) def handle_root(args_path): """Handle all paths, decide where to forward the query.""" if args_path == '' or args_path.startswith('api/') or args_path.startswith('API/') or \ args_path.startswith('rpc/') or args_path.startswith('RPC/'): if flask.request.method == 'POST': # Need to get those here because it might not be available in this aux function. request_json = flask.request.get_data().decode('utf-8') response = handle_rpc_post(request_json) return response elif flask.request.method == 'OPTIONS': response = handle_rpc_options() return response else: error = 'Invalid method.' return flask.Response(error, 405, mimetype='application/json') elif args_path.startswith('rest/') or args_path.startswith('REST/'): if flask.request.method == 'GET' or flask.request.method == 'POST': # Pass the URL path without /REST/ part and Flask request object. rest_path = args_path.split('/', 1)[1] response = handle_rest(rest_path, flask.request) return response else: error = 'Invalid method.' return flask.Response(error, 405, mimetype='application/json') else: # Not found return flask.Response(None, 404, mimetype='application/json') ###################### # JSON-RPC API ###################### def handle_rpc_options(): response = flask.Response('', 204) _set_cors_headers(response) return response def handle_rpc_post(request_json): """Handle /API/ POST route. Call relevant get_rows/create_transaction wrapper.""" # Check for valid request format. try: request_data = json.loads(request_json) assert 'id' in request_data and request_data['jsonrpc'] == "2.0" and request_data['method'] # params may be omitted except: obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest(data="Invalid JSON-RPC 2.0 request format") return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Only arguments passed as a `dict` are supported. if request_data.get('params', None) and not isinstance(request_data['params'], dict): obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data='Arguments must be passed as a JSON object (list of unnamed arguments not supported)') return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Return an error if the API Status Poller checks fail. if not config.FORCE and current_api_status_code: return flask.Response(current_api_status_response_json, 503, mimetype='application/json') # Answer request normally. # NOTE: `UnboundLocalError: local variable 'output' referenced before assignment` means the method doesn’t return anything. jsonrpc_response = jsonrpc.JSONRPCResponseManager.handle(request_json, dispatcher) response = flask.Response(jsonrpc_response.json.encode(), 200, mimetype='application/json') _set_cors_headers(response) return response ###################### # HTTP REST API ###################### def handle_rest(path_args, flask_request): """Handle /REST/ route. Query the database using get_rows or create transaction using compose_transaction.""" url_action = flask_request.path.split('/')[-1] if url_action == 'compose': compose = True elif url_action == 'get': compose = False else: error = 'Invalid action "%s".' % url_action return flask.Response(error, 400, mimetype='application/json') # Get all arguments passed via URL. url_args = path_args.split('/') try: query_type = url_args.pop(0).lower() except IndexError: error = 'No query_type provided.' return flask.Response(error, 400, mimetype='application/json') # Check if message type or table name are valid. if (compose and query_type not in API_TRANSACTIONS) or \ (not compose and query_type not in API_TABLES): error = 'No such query type in supported queries: "%s".' % query_type return flask.Response(error, 400, mimetype='application/json') # Parse the additional arguments. extra_args = flask_request.args.items() query_data = {} if compose: common_args = {} transaction_args = {} for (key, value) in extra_args: # Determine value type. try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass # Split keys into common and transaction-specific arguments. Discard the privkey. if key in COMMONS_ARGS: common_args[key] = value elif key == 'privkey': pass else: transaction_args[key] = value # Must have some additional transaction arguments. if not len(transaction_args): error = 'No transaction arguments provided.' return flask.Response(error, 400, mimetype='application/json') # Compose the transaction. try: query_data = compose_transaction(db, name=query_type, params=transaction_args, **common_args) except (script.AddressError, exceptions.ComposeError, exceptions.TransactionError, exceptions.BalanceError) as error: error_msg = str(error.__class__.__name__) + ': ' + str(error) return flask.Response(error_msg, 400, mimetype='application/json') else: # Need to de-generate extra_args to pass it through. query_args = dict([item for item in extra_args]) operator = query_args.pop('op', 'AND') # Put the data into specific dictionary format. data_filter = [{'field': key, 'op': '==', 'value': value} for (key, value) in query_args.items()] # Run the query. try: query_data = get_rows(db, table=query_type, filters=data_filter, filterop=operator) except APIError as error: return flask.Response(str(error), 400, mimetype='application/json') # See which encoding to choose from. file_format = flask_request.headers['Accept'] # JSON as default. if file_format == 'application/json' or file_format == '*/*': response_data = json.dumps(query_data) elif file_format == 'application/xml': # Add document root for XML. Note when xmltodict encounters a list, it produces separate tags for every item. # Hence we end up with multiple query_type roots. To combat this we put it in a separate item dict. response_data = serialize_to_xml({query_type: {'item': query_data}}) else: error = 'Invalid file format: "%s".' % file_format return flask.Response(error, 400, mimetype='application/json') response = flask.Response(response_data, 200, mimetype=file_format) return response # Init the HTTP Server. init_api_access_log() http_server = HTTPServer(WSGIContainer(app), xheaders=True) try: http_server.listen(config.RPC_PORT, address=config.RPC_HOST) self.is_ready = True self.ioloop.start() except OSError: raise APIError("Cannot start the API subsystem. Is server already running, or is something else listening on port {}?".format(config.RPC_PORT)) db.close() http_server.stop() self.ioloop.close() return
def __init__(self): self.project = Index() dispatcher.add_method(self.init) dispatcher.add_method(self.lint) dispatcher.add_method(self.provideDocumentSymbols) dispatcher.add_method(self.provideCompletionItems) dispatcher.add_method(self.provideSignatureHelp) dispatcher.add_method(self.provideDefinition)
def run(self): db = util.connect_to_db(flags='SQLITE_OPEN_READONLY') app = flask.Flask(__name__) auth = HTTPBasicAuth() @auth.get_password def get_pw(username): if username == config.RPC_USER: return config.RPC_PASSWORD return None ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): return get_rows(db, table=table, **kwargs) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=[]): return db_query(db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} and do_{transaction} methods def generate_create_method(transaction): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_args, private_key_wif def create_method(**kwargs): transaction_args, common_args, private_key_wif = split_params(**kwargs) return compose_transaction(db, name=transaction, params=transaction_args, **common_args) def do_method(**kwargs): transaction_args, common_args, private_key_wif = split_params(**kwargs) return do_transaction(db, name=transaction, params=transaction_args, private_key_wif=private_key_wif, **common_args) return create_method, do_method for transaction in API_TRANSACTIONS: create_method, do_method = generate_create_method(transaction) create_method.__name__ = 'create_{}'.format(transaction) do_method.__name__ = 'do_{}'.format(transaction) dispatcher.add_method(create_method) dispatcher.add_method(do_method) @dispatcher.add_method def sign_tx(unsigned_tx_hex, privkey=None): return sign_transaction(unsigned_tx_hex, private_key_wif=privkey) @dispatcher.add_method def broadcast_tx(signed_tx_hex): return broadcast_transaction(signed_tx_hex) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise Exception("block_index must be an integer.") cursor = db.cursor() cursor.execute('select * from messages where block_index = ? order by message_index asc', (block_index,)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [message_indexes,] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise Exception("All items in message_indexes are not integers") cursor = db.cursor() cursor.execute('SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]),)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_xbj_supply(): return util.xbj_supply(db) @dispatcher.add_method def get_asset_info(assets): if not isinstance(assets, list): raise Exception("assets must be a list of asset names, even if it just contains one entry") assetsInfo = [] for asset in assets: # WDC and XBJ. if asset in [config.WDC, config.XBJ]: if asset == config.WDC: supply = worldcoin.get_wdc_supply(normalize=False) else: supply = util.xbj_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'callable': False, 'call_date': None, 'call_price': None, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: break #asset not found, most likely else: last_issuance = issuances[-1] supply = 0 locked = False for e in issuances: if e['locked']: locked = True supply += e['quantity'] assetsInfo.append({ 'asset': asset, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': supply, 'callable': bool(last_issuance['callable']), 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'issuer': last_issuance['issuer']}) return assetsInfo @dispatcher.add_method def get_block_info(block_index): # debug block pruning #print(block_index) assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)) blocks = list(cursor) if len(blocks) == 1: block = blocks[0] elif len(blocks) == 0: raise exceptions.DatabaseError('No blocks found.') else: assert False cursor.close() return block @dispatcher.add_method def get_blocks(block_indexes): """fetches block info and messages for the specified block indexes""" if not isinstance(block_indexes, (list, tuple)): raise Exception("block_indexes must be a list of integers.") if len(block_indexes) >= 250: raise Exception("can only specify up to 250 indexes at a time.") block_indexes_str = ','.join([str(x) for x in block_indexes]) cursor = db.cursor() cursor.execute('SELECT * FROM blocks WHERE block_index IN (%s) ORDER BY block_index ASC' % (block_indexes_str,)) blocks = cursor.fetchall() cursor.execute('SELECT * FROM messages WHERE block_index IN (%s) ORDER BY block_index ASC, message_index ASC' % (block_indexes_str,)) messages = collections.deque(cursor.fetchall()) for block in blocks: messages_in_block = [] block['_messages'] = [] while len(messages) and messages[0]['block_index'] == block['block_index']: block['_messages'].append(messages.popleft()) assert not len(messages) #should have been cleared out cursor.close() return blocks @dispatcher.add_method def get_running_info(): latestBlockIndex = worldcoin.get_block_count() try: util.database_check(db, latestBlockIndex) except exceptions.DatabaseError as e: caught_up = False else: caught_up = True try: last_block = util.last_block(db) except: last_block = {'block_index': None, 'block_hash': None, 'block_time': None} try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'worldcoin_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in ['transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'wdcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'callbacks', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages']: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names @dispatcher.add_method def get_holder_count(asset): holders = util.holders(db, asset) addresses = [] for holder in holders: addresses.append(holder['address']) return { asset: len(set(addresses)) } def _set_cors_headers(response): if config.RPC_ALLOW_CORS: response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; @app.route('/', methods=["OPTIONS",]) @app.route('/api/', methods=["OPTIONS",]) def handle_options(): response = flask.Response('', 204) _set_cors_headers(response) return response @app.route('/', methods=["POST",]) @app.route('/api/', methods=["POST",]) @auth.login_required def handle_post(): try: request_json = flask.request.get_data().decode('utf-8') request_data = json.loads(request_json) assert 'id' in request_data and request_data['jsonrpc'] == "2.0" and request_data['method'] # params may be omitted except: obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest(data="Invalid JSON-RPC 2.0 request format") return flask.Response(obj_error.json.encode(), 200, mimetype='application/json') #only arguments passed as a dict are supported if request_data.get('params', None) and not isinstance(request_data['params'], dict): obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data='Arguments must be passed as a JSON object (list of unnamed arguments not supported)') return flask.Response(obj_error.json.encode(), 200, mimetype='application/json') #return an error if API fails checks if not config.FORCE and current_api_status_code: return flask.Response(current_api_status_response_json, 200, mimetype='application/json') jsonrpc_response = jsonrpc.JSONRPCResponseManager.handle(request_json, dispatcher) response = flask.Response(jsonrpc_response.json.encode(), 200, mimetype='application/json') _set_cors_headers(response) return response init_api_access_log() http_server = HTTPServer(WSGIContainer(app), xheaders=True) try: http_server.listen(config.RPC_PORT, address=config.RPC_HOST) self.is_ready = True IOLoop.instance().start() except OSError: raise Exception("Cannot start the API subsystem. Is {} already running, or is something else listening on port {}?".format(config.XBJ_CLIENT, config.RPC_PORT))
def add_method_with_lock(rpc_fn, *args, **kwargs): rpc_fn_with_lock = with_lock(rpc_fn) return dispatcher.add_method(rpc_fn_with_lock, *args, **kwargs)
def add_service(self, method): dispatcher.add_method(method)
def run (self): db = util.connect_to_db(flags='SQLITE_OPEN_READONLY') ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): return translate(db, table=table, **kwargs) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=[]): return db_query(db, query, tuple(bindings)) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise Exception("block_index must be an integer.") cursor = db.cursor() cursor.execute('select * from messages where block_index = ? order by message_index asc', (block_index,)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [message_indexes,] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise Exception("All items in message_indexes are not integers") cursor = db.cursor() cursor.execute('SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]),)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_xcp_supply(): return util.xcp_supply(db) @dispatcher.add_method def get_asset_info(assets): if not isinstance(assets, list): raise Exception("assets must be a list of asset names, even if it just contains one entry") assetsInfo = [] for asset in assets: # BTC and XCP. if asset in ['BTC', 'XCP']: if asset == 'BTC': supply = bitcoin.get_btc_supply(normalize=False) else: supply = util.xcp_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'callable': False, 'call_date': None, 'call_price': None, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: break #asset not found, most likely else: last_issuance = issuances[-1] supply = 0 locked = False for e in issuances: if e['locked']: locked = True supply += e['quantity'] assetsInfo.append({ 'asset': asset, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': supply, 'callable': bool(last_issuance['callable']), 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'issuer': last_issuance['issuer']}) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)) try: blocks = list(cursor) assert len(blocks) == 1 block = blocks[0] except IndexError: raise exceptions.DatabaseError('No blocks found.') cursor.close() return block @dispatcher.add_method def get_running_info(): latestBlockIndex = bitcoin.get_block_count() try: util.database_check(db, latestBlockIndex) except: caught_up = False else: caught_up = True try: last_block = util.last_block(db) except: last_block = {'block_index': None, 'block_hash': None, 'block_time': None} try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'bitcoin_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in ['transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'btcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'callbacks', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages']: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names ###################### #WRITE/ACTION API @dispatcher.add_method def create_bet(source, feed_address, bet_type, deadline, wager, counterwager, expiration, target_value=0.0, leverage=5040, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): try: bet_type_id = util.BET_TYPE_ID[bet_type] except KeyError: raise exceptions.BetError('Unknown bet type.') tx_info = bet.compose(db, source, feed_address, bet_type_id, deadline, wager, counterwager, target_value, leverage, expiration) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_broadcast(source, fee_fraction, text, timestamp, value=-1, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = broadcast.compose(db, source, timestamp, value, fee_fraction, text) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_btcpay(source, order_match_id, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = btcpay.compose(db, source, order_match_id) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_burn(source, quantity, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = burn.compose(db, source, quantity) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_cancel(source, offer_hash, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = cancel.compose(db, source, offer_hash) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_callback(source, fraction, asset, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = callback.compose(db, source, fraction, asset) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_dividend(source, quantity_per_unit, asset, dividend_asset, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = dividend.compose(db, source, quantity_per_unit, asset, dividend_asset) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_issuance(source, asset, quantity, divisible, description, callable_=None, call_date=None, call_price=None, transfer_destination=None, lock=False, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): try: quantity = int(quantity) except ValueError: raise Exception("Invalid quantity") if lock: description = "LOCK" tx_info = issuance.compose(db, source, transfer_destination, asset, quantity, divisible, callable_, call_date, call_price, description) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_order(source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, fee_provided, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = order.compose(db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, fee_provided=fee_provided, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_send(source, destination, asset, quantity, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = send.compose(db, source, destination, asset, quantity) return bitcoin.transaction(tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def sign_tx(unsigned_tx_hex, privkey=None): return bitcoin.sign_tx(unsigned_tx_hex, private_key_wif=privkey) @dispatcher.add_method def broadcast_tx(signed_tx_hex): if not config.TESTNET and config.BROADCAST_TX_MAINNET in ['bci', 'bci-failover']: url = "https://blockchain.info/pushtx" params = {'tx': signed_tx_hex} response = requests.post(url, data=params) if response.text.lower() != 'transaction submitted' or response.status_code != 200: if config.BROADCAST_TX_MAINNET == 'bci-failover': return bitcoin.broadcast_tx(signed_tx_hex) else: raise Exception(response.text) return response.text else: return bitcoin.broadcast_tx(signed_tx_hex) class API(object): @cherrypy.expose def index(self): try: data = cherrypy.request.body.read().decode('utf-8') except ValueError: raise cherrypy.HTTPError(400, 'Invalid JSON document') cherrypy.response.headers["Content-Type"] = "application/json" #CORS logic is handled in the nginx config # Check version. # Check that bitcoind is running, communicable, and caught up with the blockchain. # Check that the database has caught up with bitcoind. if not config.FORCE: try: self.last_check except: self.last_check = 0 try: if time.time() - self.last_check >= 4 * 3600: # Four hours since last check. code = 10 util.version_check(db) if time.time() - self.last_check > 10 * 60: # Ten minutes since last check. code = 11 bitcoin.bitcoind_check(db) code = 12 util.database_check(db, bitcoin.get_block_count()) # TODO: If not reparse or rollback, once those use API. self.last_check = time.time() except Exception as e: exception_name = e.__class__.__name__ exception_text = str(e) response = jsonrpc.exceptions.JSONRPCError(code=code, message=exception_name, data=exception_text) return response.json.encode() response = jsonrpc.JSONRPCResponseManager.handle(data, dispatcher) return response.json.encode() cherrypy.config.update({ 'log.screen': False, "environment": "embedded", 'log.error_log.propagate': False, 'log.access_log.propagate': False, "server.logToScreen" : False }) checkpassword = cherrypy.lib.auth_basic.checkpassword_dict( {config.RPC_USER: config.RPC_PASSWORD}) app_config = { '/': { 'tools.trailing_slash.on': False, 'tools.auth_basic.on': True, 'tools.auth_basic.realm': 'counterpartyd', 'tools.auth_basic.checkpassword': checkpassword, }, } application = cherrypy.Application(API(), script_name="/api", config=app_config) #disable logging of the access and error logs to the screen application.log.access_log.propagate = False application.log.error_log.propagate = False if config.PREFIX != config.UNITTEST_PREFIX: #skip setting up logs when for the test suite #set up a rotating log handler for this application # Remove the default FileHandlers if present. application.log.error_file = "" application.log.access_file = "" maxBytes = getattr(application.log, "rot_maxBytes", 10000000) backupCount = getattr(application.log, "rot_backupCount", 1000) # Make a new RotatingFileHandler for the error log. fname = getattr(application.log, "rot_error_file", os.path.join(config.DATA_DIR, "api.error.log")) h = logging_handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) h.setLevel(logging.DEBUG) h.setFormatter(cherrypy._cplogging.logfmt) application.log.error_log.addHandler(h) # Make a new RotatingFileHandler for the access log. fname = getattr(application.log, "rot_access_file", os.path.join(config.DATA_DIR, "api.access.log")) h = logging_handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) h.setLevel(logging.DEBUG) h.setFormatter(cherrypy._cplogging.logfmt) application.log.access_log.addHandler(h) #start up the API listener/handler server = wsgiserver.CherryPyWSGIServer((config.RPC_HOST, config.RPC_PORT), application, numthreads=config.API_NUM_THREADS, request_queue_size=config.API_REQUEST_QUEUE_SIZE) #logging.debug("Initializing API interface…") try: server.start() except OSError: raise Exception("Cannot start the API subsystem. Is counterpartyd" " already running, or is something else listening on port %s?" % config.RPC_PORT)
def application(request): dispatcher.add_method(login, name="login") dispatcher.add_method(submit, name="submit") dispatcher.add_method(transaction, name="transaction") dispatcher.add_method(transfer, name="transfer") dispatcher.add_method(queryBalance, name="queryBalance") dispatcher.add_method(queryTransaction, name="queryTransaction") response = JSONRPCResponseManager.handle( request.data, dispatcher) return Response(response.json, mimetype='application/json')
@dispatcher.add_method def simple_add(first=0, **kwargs): return first + kwargs["second"] def echo_with_long_name(msg): return msg def time_ping(): return datetime.now().isoformat() dispatcher.add_method(time_ping) dispatcher.add_method(echo_with_long_name, name='echo') dispatcher['subtract'] = lambda a, b: a - b dispatcher['dict_to_list'] = dict_to_list dispatcher.add_method(showAnchorDlg) dispatcher.add_method(hideAnchorDlg) @Request.application def application(request): response = manager.handle(request.get_data(cache=False, as_text=True), dispatcher) return Response(response.json, mimetype='application/json') def launchMainWin(): mw = MainWindow()
# Инкрементируем кол-во голосов storage.increment\ ( make_key(key, 'count') , 1 ) # Увеличиваем общую сумму голосов storage.increment\ ( make_key(key, 'sum') , score ) # Сохраняем данные голосования пользователя storage.set(user_key, score) return True else: return False dispatcher.add_method(do_vote, name='vote') def do_vote_cancel\ ( user_id , resource_id , score_range , namespace ): data = MultiDict(locals().copy()) form = forms.CommonForm(data) if not form.validate(): return {'error': form.errors} key = make_key(namespace, score_range, resource_id)
def run(self): db = util.connect_to_db(flags='SQLITE_OPEN_READONLY') ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): return translate(db, table=table, **kwargs) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=[]): return db_query(db, query, tuple(bindings)) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise Exception("block_index must be an integer.") cursor = db.cursor() cursor.execute( 'select * from messages where block_index = ? order by message_index asc', (block_index, )) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [ message_indexes, ] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise Exception( "All items in message_indexes are not integers") cursor = db.cursor() cursor.execute( 'SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]), )) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_xcp_supply(): return util.xcp_supply(db) @dispatcher.add_method def get_asset_info(assets): if not isinstance(assets, list): raise Exception( "assets must be a list of asset names, even if it just contains one entry" ) assetsInfo = [] for asset in assets: # BTC and XCP. if asset in ['BTC', 'XCP']: if asset == 'BTC': supply = bitcoin.get_btc_supply(normalize=False) else: supply = util.xcp_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'callable': False, 'call_date': None, 'call_price': None, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list( cursor.execute( '''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: break #asset not found, most likely else: last_issuance = issuances[-1] supply = 0 locked = False for e in issuances: if e['locked']: locked = True supply += e['quantity'] assetsInfo.append({ 'asset': asset, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': supply, 'callable': bool(last_issuance['callable']), 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'issuer': last_issuance['issuer'] }) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index, )) try: blocks = list(cursor) assert len(blocks) == 1 block = blocks[0] except IndexError: raise exceptions.DatabaseError('No blocks found.') cursor.close() return block @dispatcher.add_method def get_running_info(): latestBlockIndex = bitcoin.get_block_count() try: util.database_check(db, latestBlockIndex) except: caught_up = False else: caught_up = True try: last_block = util.last_block(db) except: last_block = { 'block_index': None, 'block_hash': None, 'block_time': None } try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'bitcoin_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def asset_names(): cursor = db.cursor() names = [ row['asset'] for row in cursor.execute( "SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC" ) ] cursor.close() return names @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in [ 'transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'btcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'callbacks', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages' ]: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [ row['asset'] for row in cursor.execute( "SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC" ) ] cursor.close() return names ###################### #WRITE/ACTION API @dispatcher.add_method def create_bet(source, feed_address, bet_type, deadline, wager, counterwager, expiration, target_value=0.0, leverage=5040, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): try: bet_type_id = util.BET_TYPE_ID[bet_type] except KeyError: raise exceptions.BetError('Unknown bet type.') tx_info = bet.compose(db, source, feed_address, bet_type_id, deadline, wager, counterwager, target_value, leverage, expiration) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_broadcast(source, fee_fraction, text, timestamp, value=-1, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = broadcast.compose(db, source, timestamp, value, fee_fraction, text) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_btcpay(source, order_match_id, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = btcpay.compose(db, source, order_match_id) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_burn(source, quantity, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = burn.compose(db, source, quantity) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_cancel(source, offer_hash, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = cancel.compose(db, source, offer_hash) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_callback(source, fraction, asset, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = callback.compose(db, source, fraction, asset) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_dividend(source, quantity_per_unit, asset, dividend_asset, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = dividend.compose(db, source, quantity_per_unit, asset, dividend_asset) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_issuance(source, asset, quantity, divisible, description, callable_=None, call_date=None, call_price=None, transfer_destination=None, lock=False, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): try: quantity = int(quantity) except ValueError: raise Exception("Invalid quantity") if lock: description = "LOCK" tx_info = issuance.compose(db, source, transfer_destination, asset, quantity, divisible, callable_, call_date, call_price, description) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_order(source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, fee_provided, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = order.compose(db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, fee_provided=fee_provided, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def create_send(source, destination, asset, quantity, encoding='multisig', pubkey=None, allow_unconfirmed_inputs=False, fee=None): tx_info = send.compose(db, source, destination, asset, quantity) return bitcoin.transaction( tx_info, encoding=encoding, exact_fee=fee, public_key_hex=pubkey, allow_unconfirmed_inputs=allow_unconfirmed_inputs) @dispatcher.add_method def sign_tx(unsigned_tx_hex, privkey=None): return bitcoin.sign_tx(unsigned_tx_hex, private_key_wif=privkey) @dispatcher.add_method def broadcast_tx(signed_tx_hex): if not config.TESTNET and config.BROADCAST_TX_MAINNET in [ 'bci', 'bci-failover' ]: url = "https://blockchain.info/pushtx" params = {'tx': signed_tx_hex} response = requests.post(url, data=params) if response.text.lower( ) != 'transaction submitted' or response.status_code != 200: if config.BROADCAST_TX_MAINNET == 'bci-failover': return bitcoin.broadcast_tx(signed_tx_hex) else: raise Exception(response.text) return response.text else: return bitcoin.broadcast_tx(signed_tx_hex) class API(object): @cherrypy.expose def index(self): try: data = cherrypy.request.body.read().decode('utf-8') except ValueError: raise cherrypy.HTTPError(400, 'Invalid JSON document') cherrypy.response.headers["Content-Type"] = "application/json" #CORS logic is handled in the nginx config # Check version. # Check that bitcoind is running, communicable, and caught up with the blockchain. # Check that the database has caught up with bitcoind. if not config.FORCE: try: self.last_check except: self.last_check = 0 try: if time.time( ) - self.last_check >= 4 * 3600: # Four hours since last check. code = 10 util.version_check(db) if time.time( ) - self.last_check > 10 * 60: # Ten minutes since last check. code = 11 bitcoin.bitcoind_check(db) code = 12 util.database_check( db, bitcoin.get_block_count() ) # TODO: If not reparse or rollback, once those use API. self.last_check = time.time() except Exception as e: exception_name = e.__class__.__name__ exception_text = str(e) response = jsonrpc.exceptions.JSONRPCError( code=code, message=exception_name, data=exception_text) return response.json.encode() response = jsonrpc.JSONRPCResponseManager.handle( data, dispatcher) return response.json.encode() cherrypy.config.update({ 'log.screen': False, "environment": "embedded", 'log.error_log.propagate': False, 'log.access_log.propagate': False, "server.logToScreen": False }) checkpassword = cherrypy.lib.auth_basic.checkpassword_dict( {config.RPC_USER: config.RPC_PASSWORD}) app_config = { '/': { 'tools.trailing_slash.on': False, 'tools.auth_basic.on': True, 'tools.auth_basic.realm': 'counterpartyd', 'tools.auth_basic.checkpassword': checkpassword, }, } application = cherrypy.Application(API(), script_name="/api", config=app_config) #disable logging of the access and error logs to the screen application.log.access_log.propagate = False application.log.error_log.propagate = False if config.PREFIX != config.UNITTEST_PREFIX: #skip setting up logs when for the test suite #set up a rotating log handler for this application # Remove the default FileHandlers if present. application.log.error_file = "" application.log.access_file = "" maxBytes = getattr(application.log, "rot_maxBytes", 10000000) backupCount = getattr(application.log, "rot_backupCount", 1000) # Make a new RotatingFileHandler for the error log. fname = getattr(application.log, "rot_error_file", os.path.join(config.DATA_DIR, "api.error.log")) h = logging_handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) h.setLevel(logging.DEBUG) h.setFormatter(cherrypy._cplogging.logfmt) application.log.error_log.addHandler(h) # Make a new RotatingFileHandler for the access log. fname = getattr(application.log, "rot_access_file", os.path.join(config.DATA_DIR, "api.access.log")) h = logging_handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) h.setLevel(logging.DEBUG) h.setFormatter(cherrypy._cplogging.logfmt) application.log.access_log.addHandler(h) #start up the API listener/handler server = wsgiserver.CherryPyWSGIServer( (config.RPC_HOST, config.RPC_PORT), application, numthreads=config.API_NUM_THREADS, request_queue_size=config.API_REQUEST_QUEUE_SIZE) #logging.debug("Initializing API interface…") try: server.start() except OSError: raise Exception( "Cannot start the API subsystem. Is counterpartyd" " already running, or is something else listening on port %s?" % config.RPC_PORT)
manager = JSONRPCResponseManager() # 1st way to add a method @dispatcher.add_method def echo(arg): return arg # 2nd way to add a method dispatcher["today"] = lambda: "Today is " + str(date.today().strftime("%d/%m/%Y")) def add(a, b): return a + b # 3rd way to add a method dispatcher.add_method(add, name="addition") @Request.application def application(request): response = manager.handle(request.get_data(as_text=True), dispatcher) return Response(response.json, mimetype='application/json') if __name__ == '__main__': print("--- SERVER ---") run_simple('localhost', 4000, application)
from jsonrpc import JSONRPCResponseManager, dispatcher from tf_inference import load_model, getPrediction, infer IP = '166.111.80.171' # 填写服务器的IP地址,crx的后端(background.js)需要进行相应修改 PORT = 4378 manager = JSONRPCResponseManager() estimator, tokenizer = load_model() def infer_sentence(sentence="123"): print(sentence) return infer([sentence], estimator, tokenizer) dispatcher.add_method(infer_sentence, name='infer') @Request.application def application(request): response = manager.handle(request.get_data(cache=False, as_text=True), dispatcher) res = Response(response.json, mimetype='application/x-www-form-urlencoded') res.headers['Access-Control-Allow-Origin'] = '*' return res if __name__ == '__main__': run_simple(IP, PORT, application)
def run_telenium(): Logger.info("TeleniumClient: Started at 0.0.0.0:9901") register_input_provider() dispatcher.add_method(rpc_version, "version") dispatcher.add_method(rpc_ping, "ping") dispatcher.add_method(rpc_get_token, "get_token") dispatcher.add_method(rpc_app_quit, "app_quit") dispatcher.add_method(rpc_app_ready, "app_ready") dispatcher.add_method(rpc_select, "select") dispatcher.add_method(rpc_highlight, "highlight") dispatcher.add_method(rpc_getattr, "getattr") dispatcher.add_method(rpc_setattr, "setattr") dispatcher.add_method(rpc_element, "element") dispatcher.add_method(rpc_execute, "execute") dispatcher.add_method(rpc_pick, "pick") dispatcher.add_method(rpc_click_on, "click_on") dispatcher.add_method(rpc_drag, "drag") dispatcher.add_method(rpc_send_keycode, "send_keycode") run_simple("0.0.0.0", 9901, application)
def run(self): db = util.connect_to_db(flags='SQLITE_OPEN_READONLY') app = flask.Flask(__name__) auth = HTTPBasicAuth() @auth.get_password def get_pw(username): if username == config.RPC_USER: return config.RPC_PASSWORD return None ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): try: return get_rows(db, table=table, **kwargs) except TypeError as e: #TODO: generalise for all API methods raise Exception(str(e)) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=[]): return db_query(db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} and do_{transaction} methods def generate_create_method(transaction): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_args, private_key_wif def create_method(**kwargs): try: transaction_args, common_args, private_key_wif = split_params( **kwargs) return compose_transaction(db, name=transaction, params=transaction_args, **common_args) except TypeError as e: #TODO: generalise for all API methods raise Exception(str(e)) def do_method(**kwargs): try: transaction_args, common_args, private_key_wif = split_params( **kwargs) return do_transaction(db, name=transaction, params=transaction_args, private_key_wif=private_key_wif, **common_args) except TypeError as e: #TODO: generalise for all API methods raise Exception(str(e)) return create_method, do_method for transaction in API_TRANSACTIONS: create_method, do_method = generate_create_method(transaction) create_method.__name__ = 'create_{}'.format(transaction) do_method.__name__ = 'do_{}'.format(transaction) dispatcher.add_method(create_method) dispatcher.add_method(do_method) @dispatcher.add_method def sign_tx(unsigned_tx_hex, privkey=None): return sign_transaction(unsigned_tx_hex, private_key_wif=privkey) @dispatcher.add_method def broadcast_tx(signed_tx_hex): return broadcast_transaction(signed_tx_hex) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise Exception("block_index must be an integer.") cursor = db.cursor() cursor.execute( 'select * from messages where block_index = ? order by message_index asc', (block_index, )) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [ message_indexes, ] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise Exception( "All items in message_indexes are not integers") cursor = db.cursor() cursor.execute( 'SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]), )) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_xcp_supply(): return util.xcp_supply(db) @dispatcher.add_method def get_asset_info(assets): if not isinstance(assets, list): raise Exception( "assets must be a list of asset names, even if it just contains one entry" ) assetsInfo = [] for asset in assets: # BTC and XCP. if asset in [config.BTC, config.XCP]: if asset == config.BTC: supply = bitcoin.get_btc_supply(normalize=False) else: supply = util.xcp_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'callable': False, 'call_date': None, 'call_price': None, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list( cursor.execute( '''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: continue #asset not found, most likely else: last_issuance = issuances[-1] supply = 0 locked = False for e in issuances: if e['locked']: locked = True supply += e['quantity'] assetsInfo.append({ 'asset': asset, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': supply, 'callable': bool(last_issuance['callable']), 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'issuer': last_issuance['issuer'] }) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index, )) blocks = list(cursor) if len(blocks) == 1: block = blocks[0] elif len(blocks) == 0: raise exceptions.DatabaseError('No blocks found.') else: assert False cursor.close() return block @dispatcher.add_method def get_blocks(block_indexes): """fetches block info and messages for the specified block indexes""" if not isinstance(block_indexes, (list, tuple)): raise Exception("block_indexes must be a list of integers.") if len(block_indexes) >= 250: raise Exception( "can only specify up to 250 indexes at a time.") block_indexes_str = ','.join([str(x) for x in block_indexes]) cursor = db.cursor() cursor.execute( 'SELECT * FROM blocks WHERE block_index IN (%s) ORDER BY block_index ASC' % (block_indexes_str, )) blocks = cursor.fetchall() cursor.execute( 'SELECT * FROM messages WHERE block_index IN (%s) ORDER BY block_index ASC, message_index ASC' % (block_indexes_str, )) messages = collections.deque(cursor.fetchall()) for block in blocks: messages_in_block = [] block['_messages'] = [] while len(messages) and messages[0]['block_index'] == block[ 'block_index']: block['_messages'].append(messages.popleft()) assert not len(messages) #should have been cleared out cursor.close() return blocks @dispatcher.add_method def get_running_info(): latestBlockIndex = bitcoin.get_block_count() try: util.database_check(db, latestBlockIndex) except exceptions.DatabaseError as e: caught_up = False else: caught_up = True try: last_block = util.last_block(db) except: last_block = { 'block_index': None, 'block_hash': None, 'block_time': None } try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'bitcoin_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in [ 'transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'btcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'callbacks', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages' ]: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [ row['asset'] for row in cursor.execute( "SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC" ) ] cursor.close() return names @dispatcher.add_method def get_holder_count(asset): holders = util.holders(db, asset) addresses = [] for holder in holders: addresses.append(holder['address']) return {asset: len(set(addresses))} def _set_cors_headers(response): if config.RPC_ALLOW_CORS: response.headers['Access-Control-Allow-Origin'] = '*' response.headers[ 'Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response.headers[ 'Access-Control-Allow-Headers'] = 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' @app.route('/', methods=[ "OPTIONS", ]) @app.route('/api/', methods=[ "OPTIONS", ]) def handle_options(): response = flask.Response('', 204) _set_cors_headers(response) return response @app.route('/', methods=[ "POST", ]) @app.route('/api/', methods=[ "POST", ]) @auth.login_required def handle_post(): try: request_json = flask.request.get_data().decode('utf-8') request_data = json.loads(request_json) assert 'id' in request_data and request_data[ 'jsonrpc'] == "2.0" and request_data['method'] # params may be omitted except: obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data="Invalid JSON-RPC 2.0 request format") return flask.Response(obj_error.json.encode(), 200, mimetype='application/json') #only arguments passed as a dict are supported if request_data.get('params', None) and not isinstance( request_data['params'], dict): obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data= 'Arguments must be passed as a JSON object (list of unnamed arguments not supported)' ) return flask.Response(obj_error.json.encode(), 200, mimetype='application/json') #return an error if API fails checks if not config.FORCE and current_api_status_code: return flask.Response(current_api_status_response_json, 200, mimetype='application/json') jsonrpc_response = jsonrpc.JSONRPCResponseManager.handle( request_json, dispatcher) response = flask.Response(jsonrpc_response.json.encode(), 200, mimetype='application/json') _set_cors_headers(response) return response init_api_access_log() http_server = HTTPServer(WSGIContainer(app), xheaders=True) try: http_server.listen(config.RPC_PORT, address=config.RPC_HOST) self.is_ready = True IOLoop.instance().start() except OSError: raise Exception( "Cannot start the API subsystem. Is {} already running, or is something else listening on port {}?" .format(config.XCP_CLIENT, config.RPC_PORT))