Esempio n. 1
0
    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)
Esempio n. 2
0
 def add_method(self, method):
     dispatcher.add_method(method)
Esempio n. 3
0
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
Esempio n. 4
0
    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
Esempio n. 5
0
    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))
Esempio n. 6
0
    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
Esempio n. 7
0
 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)
Esempio n. 8
0
    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))
Esempio n. 9
0
 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)
Esempio n. 10
0
 def add_service(self, method):
     dispatcher.add_method(method)
Esempio n. 11
0
    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)
Esempio n. 12
0
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')
Esempio n. 13
0

@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()
Esempio n. 14
0
            # Инкрементируем кол-во голосов
            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)
Esempio n. 15
0
    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)
Esempio n. 16
0
 def add_method(self, method):
     dispatcher.add_method(method)
Esempio n. 17
0
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)
Esempio n. 18
0
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)
Esempio n. 19
0
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)
Esempio n. 20
0
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)
Esempio n. 21
0
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
Esempio n. 22
0
    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))
            # Инкрементируем кол-во голосов
            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)