Esempio n. 1
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_history/
            Expects 'addresses[]' as request args
            'addresses[]' is an array of address

            Returns an array of WalletIndex for each address

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        wallet_index = self.manager.tx_storage.wallet_index

        if not wallet_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        addresses = request.args[b'addresses[]']

        history = []
        seen: Set[bytes] = set()
        for address_to_decode in addresses:
            address = address_to_decode.decode('utf-8')
            for tx_hash in wallet_index.get_from_address(address):
                tx = self.manager.tx_storage.get_transaction(tx_hash)
                if tx_hash not in seen:
                    seen.add(tx_hash)
                    history.append(tx.to_json_extended())

        data = {'history': history}
        return json.dumps(data, indent=4).encode('utf-8')
Esempio n. 2
0
def textReply(request: Request, status: int, message: str) -> bytes:
    """Replies to an HTTP request with a plain text message.
    The reply body is returned.
    """
    request.setResponseCode(status)
    request.setHeader(b'Content-Type', b'text/plain; charset=UTF-8')
    return message.encode()
Esempio n. 3
0
def emptyReply(request: Request) -> bytes:
    """Replies to an HTTP request with a no-body response.
    An empty bytes sequence is returned.
    """
    request.setResponseCode(204)
    request.setHeader(b'Content-Length', b'0')
    return b''
Esempio n. 4
0
    def render_POST(self, request: Request) -> bytes:
        """ POST request for /thin_wallet/address_history/

            It has the same behaviour as the GET request but when using the GET
            we have a limit of addresses to put as query param, otherwise we end up
            reaching the HTTP content length limit
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        if not self.manager.tx_storage.indexes.addresses:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        raw_body_bytes = request.content.read() or b''
        raw_body_str = raw_body_bytes.decode('utf-8')
        try:
            post_data = json.loads(raw_body_str)
        except json.JSONDecodeError:
            return get_missing_params_msg('invalid json')

        if 'addresses' not in post_data:
            return get_missing_params_msg('addresses')

        return self.get_address_history(post_data.get('addresses'),
                                        post_data.get('hash'))
Esempio n. 5
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token/

            Expects 'id' (hash) as GET parameter of the queried token

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}).encode('utf-8')

        if b'id' in request.args:
            try:
                token_uid_str = request.args[b'id'][0].decode('utf-8')
                token_uid = bytes.fromhex(token_uid_str)
            except (ValueError, AttributeError):
                return json.dumps({
                    'success': False,
                    'message': 'Invalid token id'
                }).encode('utf-8')

            data = self.get_one_token_data(token_uid)
        else:
            data = self.get_list_token_data()

        return json.dumps(data).encode('utf-8')
Esempio n. 6
0
 def response_get_process(self, session: Dict, session_id: str, request: Request):
     request.setResponseCode(OK)
     if session is None:
         session = {'auth': False}
         yield self.cache_interface.set(session_id, session)
     request.write(ujson.dumps(session).encode('utf-8'))
     request.finish()
     return None
Esempio n. 7
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token/

            Expects 'id' (hash) as GET parameter of the queried token

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}).encode('utf-8')

        if b'id' not in request.args:
            return get_missing_params_msg('id')

        try:
            token_uid_str = request.args[b'id'][0].decode('utf-8')
            token_uid = bytes.fromhex(token_uid_str)
        except (ValueError, AttributeError):
            return json.dumps({
                'success': False,
                'message': 'Invalid token id'
            }).encode('utf-8')

        try:
            token_info = self.manager.tx_storage.tokens_index.get_token_info(
                token_uid)
        except KeyError:
            return json.dumps({
                'success': False,
                'message': 'Unknown token'
            }).encode('utf-8')

        mint = []
        melt = []

        transactions_count = self.manager.tx_storage.tokens_index.get_transactions_count(
            token_uid)

        for tx_hash, index in token_info.mint:
            mint.append({'tx_id': tx_hash.hex(), 'index': index})

        for tx_hash, index in token_info.melt:
            melt.append({'tx_id': tx_hash.hex(), 'index': index})

        data = {
            'name': token_info.name,
            'symbol': token_info.symbol,
            'success': True,
            'mint': mint,
            'melt': melt,
            'total': token_info.total,
            'transactions_count': transactions_count,
        }
        return json.dumps(data).encode('utf-8')
Esempio n. 8
0
    def read_json_body(self, request: Request):
        try:
            result = ujson.loads(request.content.getvalue())
        except ValueError:
            request.setResponseCode(BAD_REQUEST)
            request.write(ujson.dumps({'status': 'error', 'message': 'Invalid JSON'}).encode('utf-8'))
            request.finish()
            result = None

        return result
Esempio n. 9
0
    def response_delete_process(self, session: Dict, session_id: str, request: Request):
        if session is None or session.get('auth', False) is not True:
            request.setResponseCode(UNAUTHORIZED)
            request.write(ujson.dumps({'status': 'error', 'message': 'Unauthorized'}).encode('utf-8'))
        else:
            yield self.cache_interface.set(session_id, {'auth': False})
            request.setResponseCode(OK)
            request.write(ujson.dumps({'status': 'success'}).encode('utf-8'))

        request.finish()
        return None
Esempio n. 10
0
    def render(self, request: Request):
        request.setHeader(b'Content-Type', b'application/json')

        if self.config.get('set_cors'):
            request.setHeader(b'Access-Control-Allow-Origin', b'*')
            request.setHeader(b'Access-Control-Allow-Methods', b'GET,POST,PUT,DELETE')
            request.setHeader(b'Access-Control-Allow-Headers', b'x-prototype-version,x-requested-with')
            request.setHeader(b'Access-Control-Max-Age', b'2520')

        callback_name = 'response_%s_process' % request.method.lower().decode('ascii')
        callback = getattr(self, callback_name, None)
        if not callback:
            request.setResponseCode(NOT_ALLOWED)
            return b'Method not allowed'

        session_id = request.getSession().uid
        d = self.cache_interface.get(session_id)
        d.addCallback(callback, session_id, request)

        return NOT_DONE_YET
Esempio n. 11
0
    def response_post_process(self, session: Dict, session_id: str, request: Request):
        body = self.read_json_body(request)

        if session and session.get('auth', False) is True:
            request.setResponseCode(OK)
            request.write(ujson.dumps({'status': 'success'}).encode('utf-8'))
        elif body and body.get('login') and body.get('password'):
            password_hash = hashlib.md5(body.get('password').encode('utf-8')).hexdigest()
            selector = Selector(Target.user, {'login': body.get('login'), 'password_hash': password_hash}, 1)
            database_request = DataBasePackage(Method.select, selector=selector)
            response = yield self.db_interface(database_request)
            result = DataBaseResponse.deserialize(response)
            if result.length == 0:
                request.setResponseCode(UNAUTHORIZED)
                request.write(ujson.dumps({'status': 'error', 'message': 'No match'}).encode('utf-8'))
            else:
                user = result.objects[0]
                session = {'login': user.login, 'auth': True}
                yield self.cache_interface.set(session_id, session)
                request.write(ujson.dumps({'status': 'success'}).encode('utf-8'))
        else:
            request.setResponseCode(BAD_REQUEST)
            request.write(ujson.dumps({'status': 'error', 'message': 'Invalid request'}).encode('utf-8'))

        request.finish()
        return None
Esempio n. 12
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token_history/

            Expects as GET parameter of the queried token:
                - 'id': token uid the history is being requested
                - 'count': int, to indicate the quantity of elements we should return
                - 'hash': string, the hash reference we are in the pagination
                - 'timestamp': int, the timestamp reference we are in the pagination
                - 'page': 'previous' or 'next', to indicate if the user wants after or before the hash reference

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}).encode('utf-8')

        if b'id' not in request.args:
            return get_missing_params_msg('id')

        try:
            token_uid_str = request.args[b'id'][0].decode('utf-8')
            token_uid = bytes.fromhex(token_uid_str)
        except (ValueError, AttributeError):
            return json.dumps({
                'success': False,
                'message': 'Invalid token id'
            }).encode('utf-8')

        if b'count' not in request.args:
            return get_missing_params_msg('count')

        count = min(int(request.args[b'count'][0]), settings.MAX_TX_COUNT)

        if b'hash' in request.args:
            if b'timestamp' not in request.args:
                return get_missing_params_msg('timestamp')

            if b'page' not in request.args:
                return get_missing_params_msg('page')

            ref_hash = request.args[b'hash'][0].decode('utf-8')
            ref_timestamp = int(request.args[b'timestamp'][0].decode('utf-8'))
            page = request.args[b'page'][0].decode('utf-8')

            if page == 'previous':
                elements, has_more = self.manager.tx_storage.tokens_index.get_newer_transactions(
                    token_uid, ref_timestamp, bytes.fromhex(ref_hash), count)
            else:
                elements, has_more = self.manager.tx_storage.tokens_index.get_older_transactions(
                    token_uid, ref_timestamp, bytes.fromhex(ref_hash), count)
        else:
            elements, has_more = self.manager.tx_storage.tokens_index.get_newest_transactions(
                token_uid, count)

        transactions = [
            self.manager.tx_storage.get_transaction(element)
            for element in elements
        ]
        serialized = [tx.to_json_extended() for tx in transactions]

        data = {
            'success': True,
            'transactions': serialized,
            'has_more': has_more,
        }
        return json.dumps(data).encode('utf-8')
Esempio n. 13
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_balance/
            Expects 'address' as request args

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        wallet_index = self.manager.tx_storage.wallet_index
        tokens_index = self.manager.tx_storage.tokens_index

        if not wallet_index or not tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        if b'address' in request.args:
            requested_address = request.args[b'address'][0].decode('utf-8')
        else:
            return get_missing_params_msg('address')

        try:
            # Check if address is valid
            decode_address(requested_address)
        except InvalidAddress:
            return json.dumps({
                'success': False,
                'message': 'Invalid \'address\' parameter'
            }).encode('utf-8')

        tokens_data: Dict[bytes, TokenData] = defaultdict(TokenData)
        tx_hashes = wallet_index.get_from_address(requested_address)
        for tx_hash in tx_hashes:
            tx = self.manager.tx_storage.get_transaction(tx_hash)
            meta = tx.get_metadata(force_reload=True)
            if not meta.voided_by:
                # We consider the spent/received values only if is not voided by
                for tx_input in tx.inputs:
                    tx2 = self.manager.tx_storage.get_transaction(
                        tx_input.tx_id)
                    tx2_output = tx2.outputs[tx_input.index]
                    if self.has_address(tx2_output, requested_address):
                        # We just consider the address that was requested
                        token_uid = tx2.get_token_uid(
                            tx2_output.get_token_index())
                        tokens_data[token_uid].spent += tx2_output.value

                for tx_output in tx.outputs:
                    if self.has_address(tx_output, requested_address):
                        # We just consider the address that was requested
                        token_uid = tx.get_token_uid(
                            tx_output.get_token_index())
                        tokens_data[token_uid].received += tx_output.value

        return_tokens_data: Dict[str, Dict[str, Any]] = {}
        for token_uid in tokens_data.keys():
            if token_uid == settings.HATHOR_TOKEN_UID:
                tokens_data[token_uid].name = settings.HATHOR_TOKEN_NAME
                tokens_data[token_uid].symbol = settings.HATHOR_TOKEN_SYMBOL
            else:
                try:
                    token_info = tokens_index.get_token_info(token_uid)
                    tokens_data[token_uid].name = token_info.name
                    tokens_data[token_uid].symbol = token_info.symbol
                except KeyError:
                    # Should never get here because this token appears in our wallet index
                    # But better than get a 500 error
                    tokens_data[
                        token_uid].name = '- (unable to fetch token information)'
                    tokens_data[
                        token_uid].symbol = '- (unable to fetch token information)'
            return_tokens_data[
                token_uid.hex()] = tokens_data[token_uid].to_dict()

        data = {
            'success': True,
            'total_transactions': len(tx_hashes),
            'tokens_data': return_tokens_data
        }
        return json.dumps(data, indent=4).encode('utf-8')
Esempio n. 14
0
 def abort(self, request: Request, code: int, message: str):
     request.setResponseCode(code)
     error = {"code": code, "message": message}
     return json.dumps(error).encode()
Esempio n. 15
0
 def response_get_process(self, session: Dict, session_id: str, request: Request):
     print(rerequest.path)
     request.setResponseCode(OK)
     request.write(b'test')
     request.finish()
     return None
Esempio n. 16
0
    async def _handle_dispatch(
        self,
        root_span: Span,
        request: Request,
        log: NotificationLoggerAdapter,
        notif: Notification,
        context: NotificationContext,
    ) -> None:
        """
        Actually handle the dispatch of notifications to devices, sequentially
        for simplicity.

        root_span: the OpenTracing span
        request: the Twisted Web Request
        log: the logger to use
        notif: the notification to dispatch
        context: the context of the notification
        """
        try:
            rejected = []

            for d in notif.devices:
                NOTIFS_RECEIVED_DEVICE_PUSH_COUNTER.inc()

                appid = d.app_id
                found_pushkins = self.find_pushkins(appid)
                if len(found_pushkins) == 0:
                    log.warning("Got notification for unknown app ID %s",
                                appid)
                    rejected.append(d.pushkey)
                    continue

                if len(found_pushkins) > 1:
                    log.warning("Got notification for an ambiguous app ID %s",
                                appid)
                    rejected.append(d.pushkey)
                    continue

                pushkin = found_pushkins[0]
                log.debug("Sending push to pushkin %s for app ID %s",
                          pushkin.name, appid)

                NOTIFS_BY_PUSHKIN.labels(pushkin.name).inc()

                result = await pushkin.dispatch_notification(notif, d, context)
                if not isinstance(result, list):
                    raise TypeError("Pushkin should return list.")

                rejected += result

            request.write(json.dumps({"rejected": rejected}).encode())

            if rejected:
                log.info(
                    "Successfully delivered notifications with %d rejected pushkeys",
                    len(rejected),
                )
        except NotificationDispatchException:
            request.setResponseCode(502)
            log.warning("Failed to dispatch notification.", exc_info=True)
        except Exception:
            request.setResponseCode(500)
            log.error("Exception whilst dispatching notification.",
                      exc_info=True)
        finally:
            if not request._disconnected:
                request.finish()

            PUSHGATEWAY_HTTP_RESPONSES_COUNTER.labels(code=request.code).inc()
            root_span.set_tag(tags.HTTP_STATUS_CODE, request.code)

            req_time = time.perf_counter() - context.start_time
            if req_time > 0:
                # can be negative as perf_counter() may not be monotonic
                NOTIFY_HANDLE_HISTOGRAM.labels(
                    code=request.code).observe(req_time)
            if not 200 <= request.code < 300:
                root_span.set_tag(tags.ERROR, True)
            root_span.finish()
Esempio n. 17
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_history/

            If 'paginate' parameter exists, it calls the new resource method
            otherwise, it will call the old and deprecated one because it's
            a request from a wallet still in an older version

            Expects 'addresses[]' as request args, and 'hash'
            as optional args to be used in pagination

            'addresses[]' is an array of address
            'hash' is the hash of the first tx of the pagination to start the history

            Returns an array of WalletIndex for each address until the maximum number

            E.g. request:

            addresses: ['WYxpdgz11cGGPSdmQPcJVwnLsUu7w5hgjw', 'WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ']

            In the case where address 'WYxpdgz11cGGPSdmQPcJVwnLsUu7w5hgjw' has 3 txs [tx_id1, tx_id2, tx_id3] and
            address 'WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ' also has 3 txs [tx_id4, tx_id5, tx_id6].

            Return: {
                'history': [array with 3 txs from first address and 2 txs from second address],
                'has_more': True, indicating that there are more txs for this request
                'first_address': 'WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ', indicating that the next request should
                                                    start with this address as first element of addresses array
                'first_hash': tx_id6, indicating that the next request should start with this transaction
            }

            So we need to execute one more request to finish getting all transactions. Request:

            addresses: ['WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ']
            hash: tx_id6

            Important note: different requests may return the same transaction for different addresses.
            We just validate if a transactions was already added in the same request, so e.g. the following case:

            1. tx1 has outputs for addr1 and addr2;
            2. Request to get [addr1, addr2];
            3. First response return txs only for addr1 including tx1;
            4. New request to get the remaining txs for addr1 and the txs for addr2 (including tx1)

            In this case we would return tx1 for both requests because we don't have the txs returned previously.
            We could send in all requests the txs already returned but it does not make much difference now.

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.wallet_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        paginate = b'paginate' in request.args and request.args[b'paginate'][
            0].decode('utf-8') == 'true'

        if paginate:
            # New resource
            if b'addresses[]' not in request.args:
                return get_missing_params_msg('addresses[]')

            addresses = request.args[b'addresses[]']

            ref_hash = None
            if b'hash' in request.args:
                # If hash parameter is in the request, it must be a valid hex
                ref_hash = request.args[b'hash'][0].decode('utf-8')

            return self.get_address_history(
                [address.decode('utf-8') for address in addresses], ref_hash)
        else:
            # Old and deprecated resource
            return self.deprecated_resource(request)
Esempio n. 18
0
    async def get(self, request: Request) -> None:
        try:
            if self.data is None:
                await asyncio.wait_for(self.read_task, timeout=30)
                if self.data is None:
                    request.setResponseCode(503)
                    request.write(b'Service temporarily unavailable')
                    return

            raw_query = urlparse(request.uri).query
            query = parse_qs(raw_query)

            # Check for etag matching
            if self.etag_base:
                hasher = hashlib.sha256()
                hasher.update(self.etag_base)
                hasher.update(raw_query)
                if request.setETag(hasher.hexdigest().encode('utf-8')):
                    # Etag matched; do not send a body
                    return

            # decode query parameter
            releases = None
            if b'releases' in query:
                releases = set(
                    self.release_aliases.get(release)
                    for release in sanitize_query_list(query[b'releases']))
                releases &= self.releases

            components = None
            if b'components' in query:
                components = sanitize_query_list(query[b'components'])
                components &= self.components

            architectures = None
            if b'architectures' in query:
                architectures = sanitize_query_list(query[b'architectures'])
                architectures.add('all')
                architectures &= self.architectures

            # generate filtered results
            def transform(item):
                result = item.copy()
                if releases is not None:
                    result['packages'] = [
                        package for package in result['packages']
                        if package['release'] in releases
                    ]
                if components is not None:
                    result['packages'] = [
                        package for package in result['packages']
                        if package['component'] in components
                    ]
                if architectures is not None:
                    result['packages'] = [
                        package for package in result['packages']
                        if package['architecture'] in architectures
                    ]
                return result, bool(result['packages'])

            result = [
                value for value, pred in map(transform, self.data) if pred
            ]

            # deliver results
            request.setHeader(b'content-type',
                              b'application/json; charset=utf-8')
            request.write(simplejson.dumps(result).encode('utf-8'))
        except Exception as e:
            log.err(
                "An exception occurred while handling request ({})".format(e))
            request.setResponseCode(400)
            request.write('Bad request'.encode('utf-8'))
        finally:
            request.finish()
Esempio n. 19
0
 def handle_redirect(request: http.Request) -> None:
     request.setResponseCode(HTTPStatus.FOUND)
     request.setHeader("location", to)
     request.finish()
Esempio n. 20
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token_history/

            Expects as GET parameter of the queried token:
                - 'id': uid of token whose history is being requested
                - 'count': int, to indicate the quantity of elements we should return
                - 'hash': string, the hash reference we are in the pagination
                - 'timestamp': int, the timestamp reference we are in the pagination
                - 'page': 'previous' or 'next', to indicate if the user wants after or before the hash reference

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}).encode('utf-8')

        parsed = parse_get_arguments(request.args, ARGS)
        if not parsed['success']:
            return get_missing_params_msg(parsed['missing'])

        if b'id' not in request.args:
            return get_missing_params_msg('id')

        try:
            token_uid = bytes.fromhex(parsed['args']['id'])
        except (ValueError, AttributeError):
            return json.dumps({'success': False, 'message': 'Invalid token id'}).encode('utf-8')

        try:
            count = min(int(parsed['args']['count']), settings.MAX_TX_COUNT)
        except ValueError:
            return json.dumps({
                'success': False,
                'message': 'Invalid \'count\' parameter, expected an int'
            }).encode('utf-8')

        if b'hash' in request.args:
            parsed = parse_get_arguments(request.args, ['timestamp', 'page', 'hash'])
            if not parsed['success']:
                return get_missing_params_msg(parsed['missing'])

            try:
                hash_bytes = bytes.fromhex(parsed['args']['hash'])
            except ValueError:
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'hash\' parameter, could not decode hexadecimal'
                }).encode('utf-8')

            page = parsed['args']['page']
            if page != 'previous' and page != 'next':
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'page\' parameter, expected \'previous\' or \'next\''
                }).encode('utf-8')

            try:
                ref_timestamp = int(parsed['args']['timestamp'])
            except ValueError:
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'timestamp\' parameter, expected an int'
                }).encode('utf-8')

            if page == 'previous':
                elements, has_more = self.manager.tx_storage.tokens_index.get_newer_transactions(
                        token_uid, ref_timestamp, hash_bytes, count)
            else:
                elements, has_more = self.manager.tx_storage.tokens_index.get_older_transactions(
                        token_uid, ref_timestamp, hash_bytes, count)
        else:
            elements, has_more = self.manager.tx_storage.tokens_index.get_newest_transactions(token_uid, count)

        transactions = [self.manager.tx_storage.get_transaction(element) for element in elements]
        serialized = [tx.to_json_extended() for tx in transactions]

        data = {
            'success': True,
            'transactions': serialized,
            'has_more': has_more,
        }
        return json.dumps(data).encode('utf-8')
Esempio n. 21
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_search/
            Expects 'address' and 'count' as required request args
            'token' is an optional parameter to define if the search wants an specific token only
            'hash' and 'page' are optional args to be used in pagination

            'address' is a base58 address string
            'count' is an integer indicating the quantity of elements to be returned
            'token' is the token uid in hex to be searched
            'hash' is the first address of the pagination to start the history
            'page' is either 'previous' or 'next' to indicate the page clicked

            Returns an array of WalletIndex until the count limit and the hash
            parameter for the next request, if has more
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        wallet_index = self.manager.tx_storage.wallet_index

        if not wallet_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        if b'address' not in request.args:
            return get_missing_params_msg('address')

        if b'count' not in request.args:
            return get_missing_params_msg('count')

        try:
            address = request.args[b'address'][0].decode('utf-8')
            # Check if address is valid
            decode_address(address)
        except InvalidAddress:
            return json.dumps({
                'success': False,
                'message': 'Invalid \'address\' parameter'
            }).encode('utf-8')

        try:
            count = min(int(request.args[b'count'][0]), settings.MAX_TX_COUNT)
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid \'count\' parameter, expected an int'
            }).encode('utf-8')

        token_uid_bytes = None
        if b'token' in request.args:
            # It's an optional parameter, we just check if it's a valid hex
            token_uid = request.args[b'token'][0].decode('utf-8')

            try:
                token_uid_bytes = bytes.fromhex(token_uid)
            except ValueError:
                return json.dumps({
                    'success':
                    False,
                    'message':
                    'Token uid is not a valid hexadecimal value.'
                }).encode('utf-8')

        hashes = wallet_index.get_from_address(address)
        # XXX To do a timestamp sorting, so the pagination works better
        # we must get all transactions and sort them
        # This is not optimal for performance
        transactions = []
        for tx_hash in hashes:
            tx = self.manager.tx_storage.get_transaction(tx_hash)
            if token_uid_bytes and not self.has_token_and_address(
                    tx, address, token_uid_bytes):
                # Request wants to filter by token but tx does not have this token
                # so we don't add it to the transactions array
                continue
            transactions.append(tx.to_json_extended())

        sorted_transactions = sorted(transactions,
                                     key=lambda tx: tx['timestamp'],
                                     reverse=True)
        if b'hash' in request.args:
            # It's a paginated request, so 'page' must also be in request.args
            if b'page' not in request.args:
                return get_missing_params_msg('page')

            page = request.args[b'page'][0].decode('utf-8')
            if page != 'previous' and page != 'next':
                # Invalid value for page parameter
                return json.dumps(
                    {
                        'success': False,
                        'message': 'Invalid value for \'page\' parameter',
                    },
                    indent=4).encode('utf-8')

            ref_hash = request.args[b'hash'][0].decode('utf-8')
            # Index where the reference hash is
            for ref_index, tx in enumerate(sorted_transactions):
                if tx['tx_id'] == ref_hash:
                    break
            else:
                # ref_hash is not in the list
                return json.dumps(
                    {
                        'success': False,
                        'message': 'Invalid hash {}'.format(ref_hash)
                    },
                    indent=4).encode('utf-8')

            if page == 'next':
                # User clicked on 'Next' button, so the ref_hash is the last hash of the list
                # So I need to get the transactions after the ref
                start_index = ref_index + 1
                end_index = start_index + count
                ret_transactions = sorted_transactions[start_index:end_index]
                # If has more transactions after this
                has_more = len(sorted_transactions) > end_index
            else:
                # User clicked on 'Previous' button, so the ref_hash is the first hash of the list
                # So I need to get the transactions before the ref
                end_index = ref_index
                start_index = max(end_index - count, 0)
                ret_transactions = sorted_transactions[start_index:end_index]
                # If has more transactions before this
                has_more = start_index > 0
        else:
            ret_transactions = sorted_transactions[:count]
            has_more = len(sorted_transactions) > count

        data = {
            'success': True,
            'transactions': ret_transactions,
            'has_more': has_more,
            'total': len(sorted_transactions),
        }
        return json.dumps(data, indent=4).encode('utf-8')
Esempio n. 22
0
    def _handle_request(self, request: Request) -> Union[int, bytes]:
        """
        Actually handle the request.
        Args:
            request: The request, corresponding to a POST request.

        Returns:
            Either a str instance or NOT_DONE_YET.

        """
        request_id = self._make_request_id()
        header_dict = {
            k.decode(): v[0].decode()
            for k, v in request.requestHeaders.getAllRawHeaders()
        }

        # extract OpenTracing scope from the HTTP headers
        span_ctx = self.sygnal.tracer.extract(Format.HTTP_HEADERS, header_dict)
        span_tags = {
            tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER,
            "request_id": request_id,
        }

        root_span = self.sygnal.tracer.start_span("pushgateway_v1_notify",
                                                  child_of=span_ctx,
                                                  tags=span_tags)

        # if this is True, we will not close the root_span at the end of this
        # function.
        root_span_accounted_for = False

        try:
            context = NotificationContext(request_id, root_span,
                                          time.perf_counter())

            log = NotificationLoggerAdapter(logger, {"request_id": request_id})

            try:
                body = json_decoder.decode(
                    request.content.read().decode("utf-8"))
            except Exception as exc:
                msg = "Expected JSON request body"
                log.warning(msg, exc_info=exc)
                root_span.log_kv({logs.EVENT: "error", "error.object": exc})
                request.setResponseCode(400)
                return msg.encode()

            if "notification" not in body or not isinstance(
                    body["notification"], dict):
                msg = "Invalid notification: expecting object in 'notification' key"
                log.warning(msg)
                root_span.log_kv({logs.EVENT: "error", "message": msg})
                request.setResponseCode(400)
                return msg.encode()

            try:
                notif = Notification(body["notification"])
            except InvalidNotificationException as e:
                log.exception("Invalid notification")
                request.setResponseCode(400)
                root_span.log_kv({logs.EVENT: "error", "error.object": e})
                return str(e).encode()

            if notif.event_id is not None:
                root_span.set_tag("event_id", notif.event_id)

            # track whether the notification was passed with content
            root_span.set_tag("has_content", notif.content is not None)

            NOTIFS_RECEIVED_COUNTER.inc()

            if len(notif.devices) == 0:
                msg = "No devices in notification"
                log.warning(msg)
                request.setResponseCode(400)
                return msg.encode()

            root_span_accounted_for = True

            async def cb():
                with REQUESTS_IN_FLIGHT_GUAGE.labels(
                        self.__class__.__name__).track_inprogress():
                    await self._handle_dispatch(root_span, request, log, notif,
                                                context)

            ensureDeferred(cb())

            # we have to try and send the notifications first,
            # so we can find out which ones to reject
            return NOT_DONE_YET
        except Exception as exc_val:
            root_span.set_tag(tags.ERROR, True)

            # [2] corresponds to the traceback
            trace = traceback.format_tb(sys.exc_info()[2])
            root_span.log_kv({
                logs.EVENT: tags.ERROR,
                logs.MESSAGE: str(exc_val),
                logs.ERROR_OBJECT: exc_val,
                logs.ERROR_KIND: type(exc_val),
                logs.STACK: trace,
            })
            raise
        finally:
            if not root_span_accounted_for:
                root_span.finish()