def _send_request(self, suffix, data=None, content_type=None): if self.url.startswith("http://"): url = "{}/{}".format(self.url, suffix) else: url = "http://{}/{}".format(self.url, suffix) headers = {} if content_type is not None: headers['Content-Type'] = content_type try: if data is not None: result = requests.post(url, headers=headers, data=data) else: result = requests.get(url, headers=headers) if result.status_code == 404: raise KeyNotFound("404") elif not result.ok: raise ClientException("Error {}: {}".format( result.status_code, result.reason)) except requests.ConnectionError as err: raise ClientException( 'Failed to connect to REST API: {}'.format(err)) return result.text
def _handle_response(self, msg_type, resp_proto, req): self._stream.wait_for_ready() future = self._stream.send(message_type=msg_type, content=req.SerializeToString()) resp = resp_proto() try: resp.ParseFromString(future.result().content) except (DecodeError, AttributeError): raise ClientException( 'Failed to parse "content" string from validator') except ValidatorConnectionError as vce: LOGGER.error('Error: %s' % vce) raise ClientException( 'Failed with ZMQ interaction: {0}'.format(vce)) data = message_to_dict(resp) # NOTE: Not all protos have this status with suppress(AttributeError): if resp.status == resp_proto.NO_RESOURCE: raise KeyNotFound("404") if resp.status != resp_proto.OK: raise ClientException("Error: %s" % data) return data
def _handle_response(self, msg_type, resp_proto, req): self._stream.wait_for_ready() future = self._stream.send(message_type=msg_type, content=req.SerializeToString()) resp = resp_proto() try: resp.ParseFromString(future.result(ZMQ_CONNECTION_TIMEOUT).content) except (DecodeError, AttributeError): raise ClientException( 'Failed to parse "content" string from validator') except ValidatorConnectionError as vce: raise ClientException( 'Failed with ZMQ interaction: {0}'.format(vce)) except (asyncio.TimeoutError, FutureTimeoutError): raise ClientException('Validator connection timeout') except Exception as e: LOGGER.exception(e) raise ClientException('Unexpected validator error') data = message_to_dict(resp) with suppress(AttributeError): LOGGER.debug(f'The response parsed data: {data}') if resp.status == resp_proto.NO_RESOURCE: raise KeyNotFound('Resource not found') elif resp.status == resp_proto.NOT_READY: raise ValidatorNotReadyException('Validator is not ready yet') elif resp.status != resp_proto.OK: raise ClientException('Error occured') return data
def _text_request(self, suffix, data=None, content_type=None): url = f"{self.url}/{suffix}" if not url.startswith("http://"): url = f"http://{url}" headers = {} if content_type is not None: headers['Content-Type'] = content_type try: if data is not None: with suppress(AttributeError): data = data.SerializeToString() result = requests.post(url, headers=headers, data=data) else: result = requests.get(url, headers=headers) if result.status_code == 404: raise KeyNotFound("404") elif not result.ok: raise ClientException("Error {}: {}".format( result.status_code, result.reason)) except requests.ConnectionError as err: raise ClientException( 'Failed to connect to REST API: {}'.format(err)) return json.loads(result.text)
def _send_transaction(self, method, data_pb, addresses_input, addresses_output): ''' Signs and sends transaction to the network using rest-api. :param str method: The method (defined in proto) for Transaction Processor to process the request. :param dict data: Dictionary that is required by TP to process the transaction. :param str addresses_input: list of addresses(keys) for which to get state. :param str addresses_output: list of addresses(keys) for which to save state. ''' addresses_input_output = [] addresses_input_output.extend(addresses_input) addresses_input_output.extend(addresses_output) addresses_input_output = list(set(addresses_input_output)) # forward transaction to test helper if self.test_helper: self.test_helper.send_transaction(method, data_pb, addresses_input_output) return payload = TransactionPayload() payload.method = method payload.data = data_pb.SerializeToString() for address in addresses_input_output: if not is_address(address): raise ClientException( 'one of addresses_input_output {} is not an address'. format(addresses_input_output)) batch_list = self.make_batch_list(payload, addresses_input, addresses_output) return get_batch_id(self._send_request('batches', batch_list, 'socket'))
def _send_request(self, suffix, data=None, conn_protocol='text', **kwargs): if conn_protocol == 'text': return self._text_request(suffix, data, **kwargs) elif conn_protocol == 'socket': return self._socket_request(suffix, data, **kwargs) raise ClientException('Unsupported connection protocol "%s"' % conn_protocol)
def _get_status(self, batch_id, wait): try: result = self._send_request( 'batch_statuses?id={}&wait={}'.format(batch_id, wait), ) return yaml.safe_load(result)['data'][0]['status'] except BaseException as err: raise ClientException(err)
def _send_transaction(self, method, data_pb, addresses_input_output): ''' Signs and sends transaction to the network using rest-api. :param str method: The method (defined in proto) for Transaction Processor to process the request. :param dict data: Dictionary that is required by TP to process the transaction. :param str addresses_input_output: list of addresses(keys) for which to get and save state. ''' payload = TransactionPayload() payload.method = method payload.data = data_pb.SerializeToString() for address in addresses_input_output: if not self.is_address(address): raise ClientException( 'one of addresses_input_output {} is not an address'. format(addresses_input_output)) batch_list = self.make_batch_list(payload, addresses_input_output) return self._send_request( "batches", batch_list.SerializeToString(), 'application/octet-stream', )
def get_signer_priv_key_from_file(keyfile): try: with open(keyfile) as fd: private_key_str = fd.read().strip() except OSError as err: raise ClientException('Failed to read private key: {}'.format( str(err))) try: private_key = Secp256k1PrivateKey.from_hex(private_key_str) except ParseError as e: raise ClientException('Unable to load private key: {}'.format( str(e))) context = create_context('secp256k1') return CryptoFactory(context).new_signer(private_key)
def generate_signer(keyfile=None): context = create_context('secp256k1') private_key = context.new_random_private_key() if keyfile: try: with open(keyfile, 'w') as fd: fd.write(private_key.as_hex()) except OSError as err: raise ClientException(f'Failed to write private key: {err}') return CryptoFactory(context).new_signer(private_key)
async def unsubscribe(request): ws = request.ws if ws is None: raise ClientException( message='Subscription available only through websocket') request.params = request.params or {} try: event_type = request.params['event_type'] except KeyError as e: raise RpcInvalidParamsError(message='Missed event_type') async with event_lock: subsevt = request.rpc._subsevt.get(ws, {}) try: del subsevt[event_type] except KeyError: raise ClientException(message='Subscription not found') return 'UNSUBSCRIBED'
def __init__(self, family_handler, keyfile=PRIV_KEY_FILE): self.url = REST_API_URL self._family_handler = family_handler try: with open(keyfile) as fd: private_key_str = fd.read().strip() fd.close() except OSError as err: raise ClientException('Failed to read private key: {}'.format( str(err))) try: private_key = Secp256k1PrivateKey.from_hex(private_key_str) except ParseError as e: raise ClientException('Unable to load private key: {}'.format( str(e))) self._signer = CryptoFactory( create_context('secp256k1')).new_signer(private_key)
def _socket_request(self, suffix, data=None): if suffix == 'batches': return self.submit_batches({'batches': data.batches}) elif 'batch_statuses?id=' in suffix: _, batch_id = suffix.split('?id=') return self.get_batch_statuses({'batch_ids': [batch_id]}) elif 'state/' in suffix: _, address = suffix.split('/') _, root = self.get_root_block() return self.fetch_state({'state_root': root, 'address': address}) else: raise ClientException('Suffix "%s" not supported' % suffix)
def validate(self, msg_id, params): from_block = params.get('from_block') swap_id = params.get('id') if from_block and not isinstance(from_block, str): raise ClientException(message='Invalid "from_block" type', msg_id=swap_id) if from_block and not BLOCK_ID_REGEXP.match(from_block): raise RpcInvalidParamsError( message='Incorrect atomic swap from block identifier.', msg_id=msg_id) if swap_id and not SWAP_ID_REGEXP.match(swap_id): raise RpcInvalidParamsError( message='Incorrect atomic swap identifier.', msg_id=msg_id) return { 'from_block': from_block, 'id': swap_id, }
def get_root_block(self): resp = self._handle_response( Message.CLIENT_BLOCK_LIST_REQUEST, ClientBlockListResponse, ClientBlockListRequest(paging=ClientPagingControls(limit=1))) block = resp['blocks'][0] header = BlockHeader() try: header_bytes = base64.b64decode(block['header']) header.ParseFromString(header_bytes) except (KeyError, TypeError, ValueError, DecodeError): header = block.get('header', None) LOGGER.error( 'The validator sent a resource with %s %s', 'a missing header' if header is None else 'an invalid header:', header or '') raise ClientException() block['header'] = message_to_dict(header) return ( block['header_signature'], block['header']['state_root_hash'], )
async def subscribe(request): ws = request.ws if ws is None: raise ClientException( message='Subscription available only through websocket') msg_id = request.msg.data['id'] request.params = request.params or {} try: event_type = request.params['event_type'] except KeyError as e: raise RpcInvalidParamsError(message='Missed event_type') try: evt_tr = EVENT_HANDLERS[event_type] except KeyError: raise ClientException(message=f'Event "{event_type}" not defined') async with event_lock: subsevt = request.rpc._subsevt.setdefault(ws, {}) if event_type in subsevt: raise ClientException( message=f'Already subscribed to event "{event_type}"') router = ws.stream.router event_types = set(subsevt.keys()) event_types.add(event_type) LOGGER.debug(f'Events to re-subsribe: {event_types}') validated_data = evt_tr.validate(msg_id, request.params) from_block = validated_data.get('from_block') if not from_block: from_block = (await router.list_blocks(limit=1))['head'] req_msg = evt_tr.prepare_subscribe_message(event_types, from_block) LOGGER.debug(f'Request message: {req_msg}') msg = await ws.stream.send( message_type=Message.CLIENT_EVENTS_SUBSCRIBE_REQUEST, message_content=req_msg.SerializeToString(), timeout=ZMQ_CONNECTION_TIMEOUT) LOGGER.debug(f'Message type: {msg.message_type}') # Validate the response type if msg.message_type != Message.CLIENT_EVENTS_SUBSCRIBE_RESPONSE: raise ClientException( message=f'Unexpected message type {msg.message_type}') # Parse the response response = ClientEventsSubscribeResponse() response.ParseFromString(msg.content) # Validate the response status if response.status != ClientEventsSubscribeResponse.OK: if response.status == ClientEventsSubscribeResponse.UNKNOWN_BLOCK: raise ClientException(message=f'Unknown block "{from_block}"') raise ClientException(message='Subscription failed: Couldn\'t ' 'send multipart') if ws not in request.rpc._evthashes: request.rpc._evthashes[ws] = set() LOGGER.debug(f'Create cosumer task for {ws}') request.rpc.loop.create_task(_consumer(request)) if event_type == 'batch': LOGGER.debug(f'Create producer task for {ws}') request.rpc.loop.create_task(_producer(request)) subsevt[event_type] = { 'msg_id': msg_id, 'validated_data': validated_data, } return 'SUBSCRIBED'
def _get_address(self, pub_key): if len(pub_key) > 64: raise ClientException("Wrong pub_key size: {}".format(pub_key)) prefix = self._get_prefix() return prefix + pub_key