async def send(conn, batch_list, timeout):
    batch_request = client_pb2.ClientBatchSubmitRequest()
    batch_request.batches.extend(list(batch_list.batches))
    batch_request.wait_for_commit = True

    validator_response = await conn.send(
        validator_pb2.Message.CLIENT_BATCH_SUBMIT_REQUEST,
        batch_request.SerializeToString(), timeout
    )

    client_response = client_pb2.ClientBatchSubmitResponse()
    client_response.ParseFromString(validator_response.content)
    resp = client_response.batch_statuses[0]

    if resp.status == client_pb2.BatchStatus.COMMITTED:
        return resp
    elif resp.status == client_pb2.BatchStatus.INVALID:
        raise ApiBadRequest('Bad Request: {}'.format(
            resp.invalid_transactions[0].message
        ))
    elif resp.status == client_pb2.BatchStatus.PENDING:
        raise ApiInternalError(
            'Internal Error: Transaction submitted but timed out.'
        )
    elif resp.status == client_pb2.BatchStatus.UNKNOWN:
        raise ApiInternalError(
            'Internal Error: Something went wrong. Try again later.'
        )
Beispiel #2
0
    async def submit_batches(self, request):
        """Accepts a binary encoded BatchList and submits it to the validator.

        Request:
            body: octet-stream BatchList of one or more Batches
            query:
                - wait: Request should not return until all batches committed

        Response:
            status:
                 - 200: Batches submitted, but wait timed out before committed
                 - 201: All batches submitted and committed
                 - 202: Batches submitted and pending (not told to wait)
            data: Status of uncommitted batches (if any, when told to wait)
            link: /batches or /batch_status link for submitted batches

        """
        # Parse request
        if request.headers['Content-Type'] != 'application/octet-stream':
            return errors.WrongBodyType()

        payload = await request.read()
        if not payload:
            return errors.EmptyProtobuf()

        try:
            batch_list = BatchList()
            batch_list.ParseFromString(payload)
        except DecodeError:
            return errors.BadProtobuf()

        # Query validator
        error_traps = [error_handlers.InvalidBatch()]
        validator_query = client_pb2.ClientBatchSubmitRequest(
            batches=batch_list.batches)
        self._set_wait(request, validator_query)

        response = await self._query_validator(
            Message.CLIENT_BATCH_SUBMIT_REQUEST,
            client_pb2.ClientBatchSubmitResponse, validator_query, error_traps)

        # Build response envelope
        data = response['batch_statuses'] or None
        link = '{}://{}/batch_status?id={}'.format(
            request.scheme, request.host,
            ','.join(b.header_signature for b in batch_list.batches))

        if data is None:
            status = 202
        elif any(s != 'COMMITTED' for _, s in data.items()):
            status = 200
        else:
            status = 201
            data = None
            link = link.replace('batch_status', 'batches')

        return self._wrap_response(data=data,
                                   metadata={'link': link},
                                   status=status)
Beispiel #3
0
    async def submit_batches(self, request):
        """Accepts a binary encoded BatchList and submits it to the validator.

        Request:
            body: octet-stream BatchList of one or more Batches
        Response:
            status:
                 - 202: Batches submitted and pending
            link: /batches or /batch_status link for submitted batches

        """
        timer_ctx = self._post_batches_total_time.time()
        self._post_batches_count.inc()

        # Parse request
        if request.headers['Content-Type'] != 'application/octet-stream':
            LOGGER.debug('Submission headers had wrong Content-Type: %s',
                         request.headers['Content-Type'])
            self._post_batches_error.inc()
            raise errors.SubmissionWrongContentType()

        body = await request.read()
        if not body:
            LOGGER.debug('Submission contained an empty body')
            self._post_batches_error.inc()
            raise errors.NoBatchesSubmitted()

        try:
            batch_list = BatchList()
            batch_list.ParseFromString(body)
        except DecodeError:
            LOGGER.debug('Submission body could not be decoded: %s', body)
            self._post_batches_error.inc()
            raise errors.BadProtobufSubmitted()

        # Query validator
        error_traps = [error_handlers.BatchInvalidTrap]
        validator_query = client_pb2.ClientBatchSubmitRequest(
            batches=batch_list.batches)

        with self._post_batches_validator_time.time():
            await self._query_validator(Message.CLIENT_BATCH_SUBMIT_REQUEST,
                                        client_pb2.ClientBatchSubmitResponse,
                                        validator_query, error_traps)

        # Build response envelope
        id_string = ','.join(b.header_signature for b in batch_list.batches)

        status = 202
        link = self._build_url(request, path='/batch_status', id=id_string)

        retval = self._wrap_response(request,
                                     metadata={'link': link},
                                     status=status)

        timer_ctx.stop()
        return retval
Beispiel #4
0
    def batches_post(self, request):
        """
        Takes protobuf binary from HTTP POST, and sends it to the validator
        """
        # Parse request
        if request.headers['Content-Type'] != 'application/octet-stream':
            return errors.WrongBodyType()

        payload = yield from request.read()
        if not payload:
            return errors.EmptyProtobuf()

        try:
            batch_list = BatchList()
            batch_list.ParseFromString(payload)
        except DecodeError:
            return errors.BadProtobuf()

        # Query validator
        error_traps = [error_handlers.InvalidBatch()]
        validator_query = client.ClientBatchSubmitRequest(
            batches=batch_list.batches)
        self._set_wait(request, validator_query)

        response = self._query_validator(Message.CLIENT_BATCH_SUBMIT_REQUEST,
                                         client.ClientBatchSubmitResponse,
                                         validator_query, error_traps)

        # Build response
        data = response['batch_statuses']
        metadata = {
            'link':
            '{}://{}/batch_status?id={}'.format(
                request.scheme, request.host,
                ','.join(b.header_signature for b in batch_list.batches))
        }

        if not data:
            status = 202
            data = None
        elif any(s != 'COMMITTED' for _, s in data.items()):
            status = 200
        else:
            status = 201
            data = None
            # Replace with /batches link when implemented
            metadata = None

        return RouteHandler._wrap_response(data=data,
                                           metadata=metadata,
                                           status=status)
Beispiel #5
0
    async def submit_batches(self, request):
        """Accepts a binary encoded BatchList and submits it to the validator.

        Request:
            body: octet-stream BatchList of one or more Batches
            query:
                - wait: Request should not return until all batches committed

        Response:
            status:
                 - 200: Batches submitted, but wait timed out before committed
                 - 201: All batches submitted and committed
                 - 202: Batches submitted and pending (not told to wait)
            data: Status of uncommitted batches (if any, when told to wait)
            link: /batches or /batch_status link for submitted batches

        """
        timer_ctx = self._post_batches_total_time.time()
        self._post_batches_count.inc()

        # Parse request
        if request.headers['Content-Type'] != 'application/octet-stream':
            LOGGER.debug('Submission headers had wrong Content-Type: %s',
                         request.headers['Content-Type'])
            self._post_batches_error.inc()
            raise errors.SubmissionWrongContentType()

        body = await request.read()
        if not body:
            LOGGER.debug('Submission contained an empty body')
            self._post_batches_error.inc()
            raise errors.NoBatchesSubmitted()

        try:
            batch_list = BatchList()
            batch_list.ParseFromString(body)
        except DecodeError:
            LOGGER.debug('Submission body could not be decoded: %s', body)
            self._post_batches_error.inc()
            raise errors.BadProtobufSubmitted()

        # Query validator
        error_traps = [error_handlers.BatchInvalidTrap]
        validator_query = client_pb2.ClientBatchSubmitRequest(
            batches=batch_list.batches)
        self._set_wait(request, validator_query)

        with self._post_batches_validator_time.time():
            response = await self._query_validator(
                Message.CLIENT_BATCH_SUBMIT_REQUEST,
                client_pb2.ClientBatchSubmitResponse, validator_query,
                error_traps)

        # Build response envelope
        data = self._drop_id_prefixes(
            self._drop_empty_props(response['batch_statuses'])) or None
        id_string = ','.join(b.header_signature for b in batch_list.batches)

        if data is None or any(d['status'] != 'COMMITTED' for d in data):
            status = 202
            link = self._build_url(request, path='/batch_status', id=id_string)
        else:
            status = 201
            data = None
            link = self._build_url(request, wait=False, id=id_string)

        retval = self._wrap_response(request,
                                     data=data,
                                     metadata={'link': link},
                                     status=status)

        timer_ctx.stop()
        return retval